Introduction
You might want an extra console for your program. However, you may realize that you can only have one console per process. Functions like AllocConsole may fail if you already have a console. Usually, solution would be like creating a console client process and using pipe to emulate the created stdin
and stdout
. You might want to write this console client program on your own, but actually it’s unnecessary. There are already great programs you may use.
PuTTY is a great implementation of console client. You may run this program for your extra consoles. In order to create a console, you may create a process with following command:
putty -serial \\.\pipe\console_pipename
In this way, the PuTTY would connect to the named pipe and become your extra console.
Control Characters
The control characters are the cornerstone of console controlling. However, only a few of them are commonly used:
Name | C Escape Sequence | Hexadecimal | Description |
Null | \0 | 0x00 | Null-Terminator of a string |
Backspace | \b | 0x08 | Move the cursor one unit backwards |
Horizontal Tab | \t | 0x09 | Advance the cursor to next tab stop |
Line Feed | \n | 0x0A | Advance the cursor to one line downwards |
Carriage Return | \r | 0x0D | Reset the cursor to the beginning of a line |
Escape | \e | 0x1B | Invoke an alternative interpretation of sequence |
The Escape Sequence
Most control characters are very common. Everyone should be familiar with \n
because we use that to change to a new line. However, the \e
sequence is not so common. Yet this control character is mostly powerful.
Most usage with escape sequence starts with Control Sequence Introducer (abbreviated as CSI), encoded as 0x5B
, the left square bracket. Following the CSI, you will put a series “parameter bytes” in the range of 0x30-0x3F
, then by a series of “intermediate bytes” in the range of 0x20-0x2F
, then eventually by a single final byte in the range 0x40-0x7E
. Common “parameter bytes” would be an array of decimal integers separated by semicolons, such as 1;2;3
(0x31 0x3B 0x32 0x3B 0x33
in hexadecimal). If there are missing numbers in the array, such as 1;;3
(0x31 0x3B 0x3B 0x33
in hexadecimal), the missing numbers will be treated as zeroes like 1;0;3
. Intermediate bytes are rare, so this blog will skip it. The final byte should be a letter or other character in the range 0x40-0x7E
.
Colored Console
To bring the console with colorful characters, you will escape the console with CSI and end the escaping with character m
, meaning Select Graphic Rendition (SGR). You may find complete introduction from Wikipedia. Please note that the colors you set on the console include two dimensions: the foreground colors and background colors. You have three options for setting colors: 4-bit (16 colors), 8-bit (256 colors) and 24-bit (true colors).
- For 4-bit colors, you start escaping the console with CSI, then follows a decimal string in range
30-37
,40-47
,90-97
or100-107
.
Range30-37
is intended for foreground color, and range90-97
is intended for bright foreground color.
Range40-47
is intended for background color, and range100-107
is intended for bright background color.
For example, you may set the console foreground with light-cyan by sending this string to the console:"\e[96m"
. - For 8-bit colors, you start escaping the console with CSI, then follows a decimal string array which starts with
38
for foreground color or48
for background color. Following38
or48
, you will add a decimal character 5 indicating that the color would be 256-color, then add the exact value of the color in decimal string.
For example, you may set the console foreground with 196th color (red) by sending this string to the console:"\e[38;5;196m"
. - For 24-bit colors, you start escaping the console with CSI, then follows a decimal string array which starts with
38
for foreground color or48
for background color. Following38
or48
, you will add a decimal character 2 indicating that the color would be true-color, then add the exact array of decimal strings to represent the color.
For example, you may set the console background withRGB(123,46,233)
, which is likely violet, by sending this string to the console:"\e[48;2;123;46;233m"
.
Cursor Positioning
In order to reposition the cursor of the console, you escape the console with CSI and end the escaping with a capital character from A
to H
. Note that missing integers will be treated as ones instead of zeroes.
- To move the cursor upward, you will end the escape sequence with capital letter
A
. Between the CSI andA
, you will put a decimal integer indicating the number of cells to move upward.
For example, you may move the cursor 4 cells upward by sending this string to the console:"\e[4A"
. - To move the cursor downward, you will end the escape sequence with capital letter
B
. Between the CSI andB
, you will put a decimal integer indicating the number of cells to move downward.
For example, you may move the cursor 7 cells downward by sending this string to the console:"\e[7B"
. - To move the cursor backward, you will end the escape sequence with capital letter
C
. Between the CSI andC
, you will put a decimal integer indicating the number of cells to move downward.
For example, you may move the cursor 3 cells downward by sending this string to the console:"\e[3C"
. - To move the cursor forward, you will end the escape sequence with capital letter
D
. Between the CSI andD
, you will put a decimal integer indicating the number of cells to move downward.
For example, you may move the cursor 8 cells downward by sending this string to the console:"\e[8D"
. - To move the cursor a few lines downward, you will end the escape the sequence with capital letter
E
. Between the CSI andE
, you will put a decimal integer indicating the number of lines to move downward. The cursor will be at the beginning of the line.
For example, you may move the cursor to the next line at the beginning by sending this string to the console:"\e[1E"
. - To move the cursor a few lines upward, you will end the escape the sequence with capital letter
F
. Between the CSI andF
, you will put a decimal integer indicating the number of lines to move upward. The cursor will be at the beginning of the line.
For example, you may move the cursor to the previous line at the beginning by sending this string to the console:"\e[F"
. - To move the cursor horizontally with absolute abscissa, you will end the escape sequence with capital letter
G
. Between the CSI andG
, you will put a decimal integer indicating the number of lines to move horizontally and absolutely.
For example, you may move the cursor to the sixteenth cell of the current line by sending this string to the console:"\e[16G"
. - To move the cursor to an absolute coordinate, you will end the escape sequence with capital letter
H
. Between the CSI andH
, you will put a pair of decimal integers indicating the coordinate.
For example, you may move the cursor to(12,44)
by sending this string to the console:"\e[44;12H"
.
Miscellaneous Operations
You may erase the display by escaping the console with CSI and end the escape sequence with capital J
. The decimal number between CSI and J
determines how the display would be erased:
- If the number is 0 or missing, erase from the cursor to the end of the screen.
- If the number is 1, erase from the cursor to the beginning of the screen.
- If the number is 2, erase the entire screen. However, the cursor is not necessarily moved.
- If the number is 3, erase the entire screen and delete all lines stored in the scroll back buffer.
You may erase the current line by escaping the console with CSI and end the escape sequence with capital K
. The decimal integer between CSI and K
determines how the line would be erased:
- If the number is 0 or missing, erase from the cursor to the end of the line.
- If the number is 1, erase from the cursor to the beginning of the line.
- If the number is 2, erase the entire line. Please note that cursor position remains as-is.
You may scroll the console page up and down by escaping the console with CSI and end the escape sequence with capital S
or T
The decimal integer between CSI and S
or T
determines how many lines would be scrolled up or down.
- If the escape sequence is ended with
S
, the page will be scrolled up. New lines are added at the bottom. - If the escape sequence is ended with
T
, the page will be scrolled down. New lines are added at the top.
Implementing Console Communications
One important thing is that PuTTY supports fully-duplex communication, meaning that you may implement a fully-duplex console, even though you don’t have to do so.
Implementing Serial Console on Bare-Metal
Traditional serial consoles are easy to implement in bare-metal environment in that the serial hardware is easy to drive. You may read the serial port introduction on OSDev wiki.
If you are running your program on UEFI, you may use EFI_SERIAL_IO_PROTOCOL to operate the serial ports. However, it is still possible to drive serial ports by using I/O instructions. In order to do so, you will have to parse AML (ACPI Machine Language) in DSDT and find resource usage of serial ports defined in ACPI namespace.
For virtual machines running on VMware, serial ports can be emulated by named pipes. You may run PuTTY on your physical machine to be the extra console for the virtual machine.
Implementing Extra Console within OS
In order to implement your extra console within an OS, the named pipe is a great choice.
For example, if you write .NET applications, you may use NamedPipeServerStream Class to create a named pipe and let PuTTY to connect to it. Since this class inherits the PipeStream Class, you may use Read
, Write
to communicate with PuTTY.
You might want to specify PipeOptions.Asynchronous
option when instantiating a named pipe for fully-duplex serial operations.
Likewise, you may use CreateNamedPipe function for Windows applications, and any similar functions on other OS platforms. The general idea is to let your program be the server of named pipe.
What if My Environment Cannot Create Named Pipes
You may set up a TCP server and use telnet
to connect to your server. Typical Linux distributions include the telnet
program. For Windows, you may install WSL, or use PuTTY.
Conclusion
This blog has briefly introduced methods of creating extra consoles for your program. You may even use ASCII-escaping to control the console’s behavior of outputting characters. To display your extra console, you may use the PuTTY program, instead of writing your own.
References
- ANSI escape code, Wikipedia: https://en.wikipedia.org/wiki/ANSI_escape_code