Chapter Outline
Terminals
Reading from and Writing to the Terminal
Terminal drivers and the General Terminal Interface
termios
Terminal output and terminfo
Detecting keystrokes
Lecture Notes
Terminals
This chapter looks at improving the user interface to our application of CD database.
We will now look at:
![]()
Reading from and Writing to the Terminal
A program can interact with the user simply by using the getchar and printf routines to read and write these default streams.
Try It Out - Menu Routines in C
Let's try and rewrite our menu routines in C, using just those two routines, calling it menu1.c.
1. Start with lines which define the array to be used as a menu and prototype the getchoice function:
![]()
2. The main function calls getchoice with the sample menu, menu:
![]()
3. Now for the important code: the function that both prints the menu and reads the user's input:
![]()
How It Works
getchoice prints the program introduction, greet, and the sample menu, choices, and asks the user to choose the initial character.
The program loops intil getchar returns a character that matches the first letter of one of the option array's entries.
When compiled and run, the program doesn't behave right.
![]()
We are getting Incorrect choice after every correct choice.
Why It Doesn't Quite Work
By default, terminal input is not made available to a program until the user presses Return.
This behavior is called canonical, or standard, mode.
We can correct the majpr deficiency in our menu routine simply by ignoring the additional line feed character with some code such as this:
![]()
Handling Redirected Output
It's very common for UNIX programs, even interactive ones, to have their input or output redirected, either to files or to other programs.
Let's see how our program behaves when we redirect its output to a file.
![]()
We can tell whether the standard output has been redirected by finding out if the low-level file descriptor is associated with a terminal.
The isatty system call does this. We pass it a valid file descriptor and it tests to see if that is currently connected to a terminal.
![]()
Try It Out - Checking for Output Redirection
1. Using the origram menu1.c, make a new include, change the main function to the following and callthe new file menu2.c.
![]()
2. Now look at the following sample output:
![]()
How It Works
The new section of code uses the isatty functionto test whether the standard output is connected to a terminal and halts if it isn't.
We can direct the error stream to a different file like this.
$ menu2 >file 2>file.error
Or combine the two output streams into a single file like this:
$$ menu2 >file 2>&1
$Talking to the Terminal
A special device, /dev/tty, is always the current terminal.
We can use normal file operations to read and write to /dev/tty.
Try It Out - Using /dev/tty
Let's modify our choice program so that we can pass parameters to the getchoice routine, to provide better control over the output.
1. Load up menu2.c and change the code to this, so that input and output come from and are directed to /dev/tty:
Now when we run the program with the output redirected, we can still see the prompts and the normal program output is separated:
![]()
The Terminal Driver and the General Terminal Interface
Sometimes, a program needs much finer control over the terminal. UNIX provides a set of interfaces that allow us to control the behavior of the terminal driver.
Overview
We can control the terminnal through a set of unction calls (the General Terminla Interface, or GTI) separate from those used for reading and writing.
![]()
In UNIX terminology, the control interface sets a 'line discipline'.
The main features that we can control are:
![]()
Hardware Model
The conceptual arrangement is to have a UNIX machine connected via a serial port to a modem and then via a telephone line and andother modem to a remote terminal.
![]()
Most 'real world' situations will form a subset of this, the most complex case.
The termios Structure
termios is the standard interface specified by POSIX and is similar to the System V interface termio.
Both are defined in the header file termios.h.
![]()
The values that can be manipulated to affect the terminal are grouped into various modes:
![]()
A minimum termios structure is typically declared like this:
![]()
The member names correspond with the five parameter types listed above.
We can initialize a termios structure for the terminal by calling the function tcgetattr, which has the prototype:
![]()
This call writes the current values of the terminal interface variables into the structure pointed to by termios_p.
If these values are then altered, we can reconfigure the terminal interface with the tcsetattr function:
![]()
The actions field for tcsetattr controls how any changes are applied.
The three possibles are:
![]()
Input Modes
The input modes control how input (characters received by the terminal driver at a serial port or keyboard) is processed before passed on to the program.
We control them by setting flags in the c_iflag member of the termios structure.
The macros that can be used for c_iflag are:
![]()
Output Modes
These modes control how output characters are processed, i.e. how characters sent form a program are processed before being transmitted to the serial port or screen.
We control output modes by setting flags in the c_oflag member of the termios structure.
The macros that we can use in c_oflag are:
![]()
Control Modes
These modes control the hardware characteristics of the terminal.
We specify control modes by setting flags in the c_cflag member of the termios structure, which has the following macros:
![]()
![]()
Local Modes
These modes control various characteristics of the terminal.
Local modes are specified by setting flags in the c_lflag member of the termios structure, with the marcos:
![]()
Special Control Characters
These are a collection of characteers, like Ctrl-C, that are acted upon in special ways when the user types them.
The c_cc array holds these characters and are used in two very different ways depending if the terminal is in canonical mode or not.
For canonical mode, the array indices are:
![]()
For non-canonical mode, the array indices are:
![]()
Since the special characters and non-canonical MIN and TIME values are so important for more advanced input character processing, we'll explain them in some detail.
Characters
![]()
The TIME and MIN Values
Together, they control what happens when a program attempts to read a file descriptor associated with a terminal.
There are four cases:
MIN = 0 and TIME = 0
In this case, the read will always return immediately.
MIN = 0 and TIME > 0
In the case, the read when any character is available to be read or when TIME has elapsed.
MIN > 0 and TIME = 0
In this cas, the read will wait until MIN characters canb e read..
MIN > 0 and TIME > 0
When read is called, it waits for a character to be received.
Accessing Terminal Modes from the Shell
If you want to see the termios settings that are currently being used while you're using the shell, you can get a list using the command:
$ stty -a
On our Linux systems, which have some extensions to the standard termios, the output is:![]()
If your version of stty supports it, you can use the command:
$ stty sane
The second method is to use the stty -g command to write the current stty setting in a form ready to re-read. On the command line, you can use:![]()
You may need to use Ctrl-J rather than Return for the final stty command. You can use the same technique in a shell script:
![]()
Setting Terminal Modes from the Command Prompt
We can use the stty command to set the terminal modes directly from the command prompt.
To set a mode in which our shell script could perform single character reads, we need to turn off canonical mode, set MIN to 1 and TIME to 0. The command is:
$ stty -icanon min 1 time 0
We can turn echo off before we prompt for the password. The command to do this is:$ stty -echo
Terminal Speed
The final function served by the termios structure is manipulating the line speed.
The four call prototypes are:
![]()
Various values are allowed for speed in the function calls above, the most important are:
![]()
There are no speeds greater than 38400 defined by the standard and no standard method of supporting serial ports at speeds greater than this.
![]()
Additional Functions
termios strutures. Their definitions are: ![]()
The functions have the following purposes:
![]()
Try It Out - A Password Program with termios
1. Our password program, password.c, begins with the following definitions.
![]()
2. Next, add in a line to get the current settings from the standard input and copy them into the termios structure that we created above.
![]()
3. Make a copy of the original settings to replace them at the end. Turn off the ECHO flag on the newrsettings and ask the user for their password:
![]()
4. Next, set the terminal attributes to newrsettings and read in the password. Then reset the terminal attributes to their orginal setting and print the password to render all the previous effort useless.
![]()
How It Works
![]()
In this example, the word hello is typed but not echoed at the Enter password: prompt. No output is produced until the user presses Return.
Try It Out - Reading Each Character
1. We insert a new header file atthe top of the old menu4.c program.
![]()
2. Then we need to declare a couple of new variables in the main function:
![]()
3. We need to change the terminal's characteristics before we call the getchoice function, so that's where we place these lines:
![]()
4. We should also return the settings to their original values before exiting:
![]()
5. We need to check against carriage returns \r now that we're in non-canonical mode, because the default mapping of CR to LF is no longer being performed.
![]()
6. To disable the Ctrl-C command for terminate, we clear the ISIG flag in the local modes. Add the following line to main.
![]()
How It Works
Now, our program gives immediate response and the character we type isn't echoed.
![]()
Terminal Output
We used printf to output characters to the screen, but with no way of placing the output at a particular position on the screen.
Terminal Type
Historically, there have been a very large number of terminals form different manufacturers.
![]()
A package known as terminfo so that a program looks up a database of terminal types. This is integrated with another package called curses.
Identify Your Termnal Type
The UNIX environment contains a variable, TERM, that is set to the type of terminal being used.
A user can query the shell to discover the system's idea of the terminal he or she is using.
$ echo $TERM
The terminfo package contains a database of capabilities and escape sequences for a large number of terminals and provides a uniform programming interface for using them.
xterm
$Here's an example terminfo file for the VT100 terminal:
![]()
Things get slightly more complicated where the escape sequence needs some parameters.
For example, a VT100 terminal uses the sequence Esc-[-
-;-
-H to move the cursor to a specified locaton. In terminfo source format, this is written with the rather inimidating cup=\E[%i%p1%d;%p2%dH$<5>. This means:
![]()
This seems rather more complex, but allows for the parameters to be in a fixed order.
![]()
Using terminfo Capabilities
We need to initialize a TERMINAL structure for the current terminal type.
We do it using terminfo and calling setupterm.
We call setupterm like this:
![]()
The setupterm library function sets the current terminal type to that specified by the parameter term.
The function outcome is stored in the integer variable pointed to by errret.
The value written will be:
![]()
The setupterm will print a diagnostic message and exit the program if it fails, as in this example:
![]()
The output from running thois program should be similar to this.
![]()
Once we've called setupterm, we can access the terminfo capabilities with three function calls, one for each of the capability types:
![]()
Let's use the terminfo database to find out the size of the terminal by retrieving the cols and lines capabilities with this program, sizeterm.c:
![]()
If we run the program inside a window on a workstation, we'll get answers that reflect the current window's size:
![]()
We can substitute the parameters in a capability with actual values using the tparm function.
![]()
Outputting Control Strings to the Terminal
Once we've constructed the terminal escape sequence with tparm, we must send it to the terminal.
Use one of the special functions provided that correctly process any required delays while the terminal completes an operation.
These functions are:
![]()
So, to move to row 5, column 30 of the screen, we can use a block of code like this:
![]()
The tputs function is provided for those situations when the terminal isn't accessed via stdout and allows you to specify the function to be used for outputting the characters.
![]()
Try It Out - Total Terminal Control
We can rewrite the getchoice function from menu4.c to give us total terminal control.
The main function has been omitted because it isn't changed. Other differences from menu4.c are highlighted.
![]()
How It Works
The rewritten getchoice function implements the same menu, but the output routines are modified to make use of the terminfo vapabilities.
If you want to see the You have chosen: message for more than a moment before the screen is cleared ready for the next selection, add a call to sleep in the main function.
![]()
Detecting Keystrokes
UNIX doesn't have an equivalent of the kbhit to detect whether a key has been pressed without actually reading it.
Try It Out - Your Very Own kbhit
However, when you're porting programs from MS-DOS, it's often convenient to emulate kbhit, which you can do using the non-canonical input mode.
1. We begin with standard headings and declare a couple of structures for the terminal settings.
peek_character is used in the test of whether or not a key has been pressed.
![]()
2. The main function calls init_keyboard to configure the terminal, then just loops once a second calling kbhit.
If the key hit is q, close_keyboard returns the behavior to normal and the program exits.
![]()
3. init_keyboard and close_keyboard configure the terminal at the start and end of the program.
![]()
4. Now for the function that checks for the keyboard hit:
![]()
5. The character pressed is read by the next function, readch, which then resets peek_character to -1 for the next loop.
![]()
When we run the program, we get:
![]()
How It Works
The terminal is configured in init_keyboard to read one character before returning (MIN=1.TIME=0). kbhit changes this behavior to check for input and return immediately (MIN=0,TIME=0) and then restores the original settings before exiting.
Summary
In this chapter we've learned about three different aspects of controlling the terminal.
CS 248 - UNIX Programming Web Site Menu
Information | Syllabus | Schedule | Online "Lectures" | Projects | Quizzes | Web Board
Copyright © 2001 by James L. Fuller, all rights reserved.