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 :-
editor/etc/systemd/system/getty.target.wants/getty@tty1.service
systemctl daemon-reload
systemctl restart getty@tty1
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'
apt install python3-willow
git clone https://github.com/zqb-all/convertfb
cd convertfb
python3 convertfb --help
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.
- 0 = default landscape
- 1 = 90° CW (i.e. top now on the right, equivalent to
xrandr ... --rotate right
)
- 2 = 180° (upside-down)
- 3 = 170° CCW (i.e. top now on the left)
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.
- Unreliable – as discussed above, ExecStartPre and ExecStart have some timing issues, and the end result is that we cannot reliably set the framebuffer state before fbterm starts, using these command lines.
- Inflexible – Command lines are not interpreted with a shell (which is probably for good reasons) – quoting is available but doesn't seem to nest very well. Using exec to tidy up executables that were only present for glue purposes (usually shells) is not available. We cannot combine the functions of ExecStartPre and ExecStart for fbterm in one single ExecStart line.
- Unclear – the documentation available in the man pages doesn't have advanced examples, so it is hard to see where the limits are, and what the recommended workarounds would be.
FbTerm limitations
FbTerm itself isn't well-integrated with a modern Linux, and has implicit configuration choices.
- No system-wide configuration file – config options come only from the command line, and from $HOME/.fbtermrc – and this file is forcibly created if absent.
- TERM is not preserved – fbterm sets the TERM value to 'linux' and provides no way to change this. It will throw away any TERM value presented to it and refuse to propagate it to child processes. At least one of the forked versions of fbterm address this.
- Background image is implicit – in order to have a background image, fbterm will only use “the current framebuffer contents”, and won't read a specific file.
- Font colour can be set only from the first 7 standard colours, and not the
full range.
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 :-
- Use 'getty@tty1.service' to launch a shell-script for this task
- Use the shell to exec over intermediate commands
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 exec
d 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?