FbTerm on Debian 12

I've always liked being able to use the text console as an alternative to starting a full graphical environment; but by default on Linux the console's capabilities are pretty limited, especially in terms of available fonts.

But, if we replace the default terminal with FbTerm (https://code.google.com/archive/p/fbterm/), we can now use all of the (monospaced) fonts we're used to seeing in graphical environments. And at a significantly higher resolution, giving us more variation over sizes.

Debian 12's defaults

The console is initialised during boot, and we can set the font using an interactive tool with dpkg-reconfigure console-setup, or by editing /etc/default/console-setup. Then run setupcon to make the changes active.

There are only a small number of fonts (Fixed, Terminus, VGA) by default, and each comes only in a few sizes. See /usr/share/consolefonts/ for the original font files.

FbTerm

Instead of the default console environment, we can install and run fbterm. We can add a couple of nice fonts at the same time too ... which of course will be available for use in your graphical environment as well.

apt install fbterm fonts-firacode fonts-hack

The console's login: prompt is kicked off by the systemd getty service. See /etc/systemd/system/getty.target.wants/getty@tty1.service for the ExecStart line, which reads :

ExecStart=-/sbin/agetty -o '-p -- \\u' --noclear - $TERM

This line means that agetty will prompt for a username, and then run /bin/login, passing the collected username along as a parameter (the \\u part of the commandline).

The change we need

Because I can't see a way to get login to start fbterm for us, we will instead ask agetty to run fbterm before running login; and that means that we need to pass the responsibility of asking for the username along to login instead of within agetty.

We achieve this by telling agetty that the login program executable is 'fbterm', and then passing options fbterm to ask it to execute the original login.

This will mean that the “Security Notes” section of the fbterm man page doesn't apply to us – we will be running fbterm as root, rather than as a user. There is no need to add named capabilities to the executable.

Experimenting with getty

If you want to try a few different possibilities with the getty configuration, you'll find yourself going through these commands a few times :-

This is the most basic command that works to activate fbterm and invoke login correctly :-

ExecStart=-/sbin/agetty --skip-login --login-program /usr/bin/fbterm --login-options /bin/login - -

Setting FbTerm options

Because the systemd service is running as root in order to be able to use 'login', when FbTerm starts it will only look for configuration from the command arguments, or in /root/.fbtermrc. This isn't ideal, but we can work with it.

Select the specific font ordering and size that you want in the .fbtermrc file :-

font-names=Fira Code, Hack, mono
font-size=13

So that we can confirm FbTerm's behaviour, we add the --verbose option to the login-options (which now have to be 'quoted'); this will give us the active font list and other settings on the console, above the login prompt. We're also including -- as the explicit marker between the options for fbterm itself, and the command/arguments for it to run.

ExecStart=-/sbin/agetty --skip-login --login-program /usr/bin/fbterm --login-options '--verbose -- /bin/login' - -

Verbose only reports font-size as 'width' and 'height'. At least we get a value for the terminal size in characters. On my system in mode 1920x1080-32bpp, using Fira Code at size 13, we end up with width 8, height 16 and 240x67 characters. At size 14, it's 9x17 and 213x63; at 16 we get 10x20 and 192x54.

Because of the way that 'login' times out if you don't interact with it, and that 'agetty' will automatically restart the command after the timeout, if you edit the '.fbtermrc' file any saved changes will be automatically deployed on your console a few seconds later without any other interaction needed.

Setting $TERM for 256 colours

FbTerm supports 256 colours, and ships a relevant termcap entry for 'fbterm', but by default it selects a TERM value of 'linux' that does not let other programs know these features are available.

We should be able to set the correct TERM value in the systemd service file with the config line Environment=TERM=fbterm; or with agetty's command-line (currently it is just the final '–' in the examples above). Unfortunately, while these work just fine in setting the environment for fbterm itself (as confirmed by looking in /proc/<pid>/environ), this value is not passed onwards to the login program, and therefore does not end up in our eventual shell. This seems to be a limitation of fbterm itself; TERM will default to 'linux'.

The way to fix this is to ask fbterm to invoke a shell capable of explicitly setting an environment variable, and then use that to start login. The hard part here is that the systemd.service ExecStart lines provide an environment that feels like it is capable of many things, where in practice there are some strong limitations that don't appear to be clearly documented.

However, we can get fbterm to run a shell, and ask the shell to set a value for TERM before running login, but only if we limit ourselves to a single variable with no spaces in the value, because any additional quoting seems to reliably break the command line parsing ... we cannot even use or add a space around the ; character.

ExecStart=-/bin/agetty --skip-login --login-program /usr/bin/fbterm --login-options '--verbose -- /usr/bin/sh -c TERM=fbterm;/bin/login' - -

Alternative approaches we did not need, or that did not work

According to the man page for 'login', we can pass ENV=VAR parameters at the end of the command arguments, but this seems to be silently ignored.

/etc/ttytype is a documented mechanism to set TERM, but by default Debian does not enable this. In any case, setting the commented TTYTYPES_FILE in /etc/login.defs and populating /etc/ttydefs doesn't have the desired outcome – I'm not sure that login(1) is even consulting ttytype at all; I didn't see any access of it via strace. The man page for login.defs notes that much of its functionality is provided by PAM instead, but I haven't seen any way to get PAM to set an environment variable conditionally.

If we follow the advice in https://superuser.com/questions/438950/how-do-i-make-ubuntu-start-fbterm-in-the-tty-on-startup we will create a new command (preferably in /usr/local/bin rather than /usr/sbin!) that manually sets TERM using a shell as an intermediate to calling login. This feels a little wasteful; but if we want to do more than setting a single environment variable with no spaces in it, then that approach is probably justified. However ... this is the approach that we eventually have to use for the more complex use-cases.

.profile is a thing

We could also edit our own .profile to fix the TERM value problem – after all, this is probably going to end up being used on a single-person computer anyway, so fixing the problem in the user's session rather than system-wide is a valid choice.

Detecting that we're running on the console under fbterm turns out to be less straightforward than you might expect. Because of the architecture of fbterm, our actual tty is a pty, just the same as it is for terminal sessions within a graphical desktop, so I can't make a condition based on detecting the use of /dev/tty*.

I can look up my process tree, and see that my current shell is parented by fbterm and login, so some approach based on the output of ps or by looking in /proc/<pid>/{stat,status} might help. It seems a bit overly complex though. If 'pstree' is installed (from the 'psmisc' package) we can use the '-s' option to get a single-line output with all the parent process names in it, pstree -s $$ will present systemd---fbterm---login---bash---pstree.

Another approach would be to note that where FbTerm sets TERM=linux, the graphical terminals often have some way to set their own TERM values that are different. We tend to get defaults related to 'xterm' when running under X.11. We could also look for the presence of the DISPLAY environment variable, which isn't set for console logins; but we might end up mistaking our session for something like an incoming ssh connection.

So I could pop a simple conditional into .profile, and change any instance of 'TERM=linux' into 'TERM=fbterm', leaving other values alone. In bash, that's a concise TERM=${TERM/linux/fbterm} using parameter expansion. If you use a different shell you'll have to make sure your approach is appropriate, but bash is the default Debian 12 shell so I'm comfortable with that.

Background Image

One of the advertised features of FbTerm is the ability to set a background image and layer the text console over the top of it. To achieve this we are going to have to reach outside the Debian packaged files, as I haven't found anything with the necessary features yet.

The FbTerm documentation recommends a program called 'fbv' available from http://s-tech.elsat.net.pl/fbv/, but although archive.org claims to have a copy from only a few days ago, I can't get to it now. There are a few forked copies on places like GitHub, and there is a copy maintained by Arch Linux.

However, a simpler program written in Python is 'convertfb', from https://github.com/zqb-all/convertfb, and has a minimal dependency on Python3's Pillow library, available in Debian from 'python3-willow'

If asking for '—help' results in a TabError from python, then https://github.com/zqb-all/convertfb/issues/4 has probably not been fixed yet. Remove the unwanted space character from the front of line 57 with sed -ie '57s/^ //' convertfb.

Pick a source image to try with your console. 'convertfb' can truncate an image's width and height to your specifications, but you may prefer to use other tools to get the source image to the right size first. You'll need to know the size of your console, which is provided by the '—verbose' output from fbterm when it starts, as '[screen] ... mode: width x height – bitdepth'.

'convertfb' will take a very wide range of input image types thanks to the Python Imaging Library. It will map each pixel into separate Alpha, Red, Green and Blue values, and then output them in a simple bitmap, in the format you select.

The right format for the framebuffer is probably 'rgba' but some hardware might be different. If you want to check on your own hardware, install the 'fbset' package, and run fbset to see what the current settings are.

All we have to do now is to decide where to store the image, and for this I've chosen /etc/fbterm.background.fb.

python3 ./convertfb -i sourceimage -o /etc/fbterm.background.fb -f rgba

In the 'getty@tty1.service' file, add the following two lines to the '[Service]' definition, just above our new ExecStart is probably sensible :-

ExecStartPre=-/usr/bin/cp /etc/fbterm.background.fb /dev/fb0
Environment=FBTERM_BACKGROUND_IMAGE=1

I haven't found the setting of the background this way to be particularly reliable. Sometimes the image is present, sometimes just a white background, and sometimes a default terminal banner has been caught. I suspect the problem is down to timing between the ExecStartPre command and the ExecStart one, allowing other processes to access the framebuffer and change the state.

Screen rotation

FbTerm can rotate its display, so you can use your console in portrait mode as well as in the default landscape.

To do this, set the option -r, --screen-rotate= to 0,1,2 or 3.

Mouse integration with gpm

The 'General Purpose Mouse interface' packaged in Debian as 'gpm' works just fine with FbTerm, no additional configuration is needed; just install the package. Left-click-drag or double-left-click to select text, right-click to paste it. There are probably more features but those are the essential ones!

See also

Problems

We have a few problems with this approach, or places where we'd want to have improvements made.

systemd

First of all, the 'systemd.service' execution environment doesn't seem to be reliable, and it certainly isn't flexible enough to perform all the tasks needed for running FbTerm correctly.

FbTerm limitations

FbTerm itself isn't well-integrated with a modern Linux, and has implicit configuration choices.

virtual consoles conflict

There is still a conflict with the pre-existing virtual consoles – although FbTerm allows the use of multiple terminal sessions within itself, if we use the Alt-F{1,2,3...} or Alt-{Right,Left} keys, the original system configuration kicks in and getty spawns fbterm on the next /dev/tty. This causes a problem with putting the background image onto /dev/fb0, which may overwrite another terminal's view.

You could try very hard to not accidentally invoke these ttys ... or you could disable them, by editing /etc/systemd/logind.conf and changing two settings in the '[Login]' section :-

NAutoVTs=1
ReserveVT=1

You can then activate this setting with systemctl restart systemd-logind but that might not clear up any existing terminals; for that a reboot is simplest but loginctl is the tool to manually fix things.

There can be only one /dev/fb0

fbterm holds /dev/fb0 open while running; framebuffer-aware browsers like links2, w3m-img and so on also want to have access to /dev/fb0 while running, which they cannot get (actually, they can't get it when they're running in a pty rather than on a tty – so they won't work inside tmux panes either). So no inline-graphics on an fbterm console, I guess.

/dev/console

By default, /dev/console will be hooked up to the currently active tty, and therefore the kernel printk messages will occasionally show up overwriting the state of your display; FbTerm seems to freeze output when this happens, but you can regain control quickly by just switching the display to a different tty and back again (e.g. with Alt-Right, Alt-Left).

We probably should divert /dev/console elsewhere to avoid this problem; but I'm not sure how this is done without building a new kernel. That seems like an overly-complex change.

Font selection

Fira Code is a great font, but we don't seem to have any way to ask for the optional stylistic sets to be switched on. We may need to build our own copy of the font with these options permanently enabled.

Conclusion: Use the shell, Luke

I've worked hard to try to get 'systemd' to work with FbTerm here, because that's apparently the “right” way to run a Linux these days. But we can still use some good old-fashioned unix skills to get an easier-to-manage and arguably “better” result :-

So, we don't need any ExecStartPre or Environment settings in the service file, just a simple ExecStart line

ExecStart=/sbin/agetty --skip-login --login-program /usr/local/bin/fbterm-login - $TERM

The script '/usr/local/bin/fbterm-login' does everything we need, all in one place and in one well-known environment :-

#!/usr/bin/sh
export TERM=fbterm
export FBTERM_BACKGROUND_IMAGE="/etc/fbterm.background.fb"

clear
cp $FBTERM_BACKGROUND_IMAGE /dev/fb0
exec /usr/bin/fbterm --verbose --\
    /usr/bin/sh -c \
    "TERM=$TERM FBTERM_CONSOLE=true exec login -p"

When we get our shell, we have a simple process tree above us, and an environment variable (FBTERM_CONSOLE) that unambiguously identifies how we got here. Intermediate shells that have been created while getting to the final executable have been execd over. The provision of the background image is much more reliable than before – i.e. it actually works.

$ pstree -s $$
systemd---fbterm---login---bash---pstree

Conclusion

Is FbTerm worth having?

It's a relatively small change to the system configuration, and provides a much more visually interesting environment for the system console, at the cost of changing/disabling the keystrokes usually used to invoke new terminals.

Are you really going to spend a lot of time running programs in the console? Yes, tmux exists and so does byobu, and they can replicate much of the 'windowed' world, but you won't get graphics inside them anyway, and you won't get graphics under FbTerm at all.

I do like text UI programs; but I can run them under a tiling window manager under X and get a better user experience. I can have different windows running with different text sizes, for example. You can run X under framebuffer if you want to ...

If your system resources are so tight that you can't run X, and you don't need graphics, then setting up FbTerm is probably a good thing. Otherwise ... why change the defaults for a text console that you won't be using?