Chapter 24. Terminal Handling with S-Lang

The S-Lang library, written by John E. Davis, provides midlevel access to terminals. It encapsulates all the low-level terminal handling through a set of routines that allow direct access to the video terminal and automatically handle scrolling and color. Although little direct support for windows is present and no widgets (or controls) are available in the S-Lang library, S-Lang provides a suitable base for such services.[1]

S-Lang is also available under DOS, which makes it attractive for applications that must be built for both Unix and DOS platforms. John E. Davis based his jed editor on S-Lang, allowing it to work on a wide variety of platforms, including DOS.

S-Lang’s terminal handling abilities fall into two categories. First, it provides a set of functions for reading keystrokes from the terminal in a controlled manner. Second, it provides routines for full-screen output to the terminal. These routines hide many terminal capabilities from the programmer but still take advantage of each terminal’s abilities.[2] This chapter discusses how to use S-Lang in each of these capacities and ends with an example program that uses both.

Input Handling

Terminal input-handling is one of the least portable subsystems in the Unix world. BSD sgtty, System V termio, and POSIX termios have all been widely used. The S-Lang library hides this complexity through a few functions designed to make keyboard handling more straightforward and portable.

Writing a program to read one character at a time from the terminal and print each character onto its own line is fairly straightforward:

 1: /* slecho.c */
 2:
 3: #include <ctype.h>
 4: #include <slang/slang.h>
 5: #include <stdio.h>
 6:
 7: int main(void) {
 8:     char ch = '';
 9:
10:     /*
11:        Start SLANG tty handling, with:
12:         -1 default interrupt character (normally, Ctrl-C)
13:          0 no flow control, allowing all characters (except
14:            interrupt) to pass through to the program
15:          1 enable OPOST output processing of escape sequences
16:     */
17:     SLang_init_tty(-1, 0, 1);
18:
19:     while (ch != 'q') {
20:         ch = SLang_getkey();
21:         printf("read: %c 0x%x
", isprint(ch) ? ch: ' ', ch);
22:     }
23:
24:     SLang_reset_tty();
25:
26:     return 0;
27: }

This program assumes that /usr/include/slang contains all the S-Lang header files. If this is not the case for your system, you need to change the code (and all other examples in this chapter) appropriately. When you compile and link this sample program, be sure to add -lslang to the link command so that the linker finds the S-Lang functions.

Initializing S-Lang Input Handling

Before any other input handling functions can work, the terminal must be placed in the state that S-Lang expects through the SLang_init_tty() function:

int SLang_init_tty(int abort_char, int flow_ctrl, int opost);

The first parameter to SLang_init_tty() is an abort character to use. If -1 is passed, the current tty interrupt character (usually, Ctrl-C) is retained; otherwise, the interrupt character is set to the value passed. Whenever the abort character is entered on the terminal, the kernel sends a SIGINT to the process, which normally terminates the application. Chapter 12 discusses how to handle signals such as SIGINT.

The next parameter turns flow control on and off. Terminal-level flow control allows the user to pause output to the terminal to prevent scrolling and then restart it. Normally, Ctrl-S is used to suspend terminal output and Ctrl-Q enables it. Although this feature is convenient for some line-oriented utilities, programs that take advantage of S-Lang are normally screen-oriented, so it may not be necessary. S-Lang allows applications to turn off this capability, which allows the program to use the Stop and Start keystrokes for other commands. To enable flow control, pass a nonzero value as the second parameter to SLang_init_tty().

The final parameter enables output post processing on the terminal. All the kernel’s post processing mechanisms are enabled if the final parameter is nonzero. See page 380 for information on output processing.

Restoring the Terminal State

Once the terminal state has been modified by SLang_init_tty(), the program must explicitly restore the terminal to its original state before exiting. If you do not do this, the terminal will be extremely difficult to use after your program exits. SLang_reset_tty() does not take any arguments, nor does it return one.

If you are writing a program that should suspend properly (normally when the user presses Ctrl-Z), this function also needs to be called when SIGTSTP is received. For more information on handling SIGTSTP properly, see Chapter 15.

When you are developing programs with S-Lang, the program will likely crash more than once during your development, leaving your terminal in a nonstandard state. You can easily fix this by running the command stty sane.

Reading Characters from the Terminal

Once the terminal has been initialized properly, reading single keystrokes is straightforward. The function SLang_getkey() returns a single character from the terminal. However, that does not mean it returns a single keystroke—in Unix, many keystrokes return multiple characters. For example, on a VT100 terminal (as well as on many other terminals, including the Linux console) pressing F1 sends four characters to the screen—ESC [ [ A (try running slecho and pressing F1 to see what characters it received). Those multiple-character sequences can be mapped to keystrokes via the terminfo database [Strang, 1991B].

SLang_getkey() waits indefinitely for a character to be present before returning. If an error occurs, the function returns 0xFFFF rather than a valid character.[3]

Checking for Pending Input

In many cases, you want to check for available characters without blocking. This is handy whenever a program needs to do background processing while polling the user for input (this is especially popular in video games). SLang_input_pending() is defined as follows:

int SLang_input_pending(int timeout);

SLang_input_pending() returns true if characters become available within n tenths of seconds. It returns as soon as the characters are available; it returns false if no characters become available within the timeout period. If a timeout period of zero is given, SLang_input_pending() tells whether characters are currently available.

This behavior is easy to see. Just replace the test on the while loop in slecho.c with

while (ch != 'q' && SLang_input_pending(20))

The program now waits a maximum of two seconds for more input. Once two seconds pass without any input, it exits.

Output Handling

Internally, S-Lang’s terminal output functions are divided into two sets: terminal-handling functions (the SLtt family) and higher-level screen management functions (the SLsmg family).

The SLtt function family works directly with the terminal; it includes functions that map closely with capabilities defined in the terminal database. It also includes routines for defining foreground and background color pairs and turning the cursor on and off. Only a few of these functions are normally used by application programmers; the rest are called internally by S-Lang.

The SLsmg family provides a higher-level abstraction of the display terminal. Although these functions use the SLtt functions for terminal handling, they provide a much more powerful interface for application developers.

These functions include string output, line drawing, and screen-querying functions. For performance reasons, these routines write to an internal buffer rather than directly to the terminal. When the application instructs S-Lang to update the physical terminal, S-Lang compares the new display to the original one and optimizes the output sequence appropriately.

Initializing Screen Management

Before using S-Lang’s terminal output functions, a program must tell SLang to look up the current terminal (as determined by the TERM environment variable) in the terminal database. This is done by calling

void SLtt_get_terminfo(void);

One of the primary functions of SLtt_get_terminfo() is to set the physical size of the screen to the size listed in the terminal database. The number of rows and columns on the terminal are stored in SLtt_Screen_Rows and SLtt_Screen_Cols, respectively. Although the terminal database is often correct, resizeable terminals (such as xterms) are popular these days, and once they are resized from the defaults, the terminal database no longer contains the correct size for the terminal. To compensate for this, S-Lang allows programs to reset the values of SLtt_Screen_Rows and SLtt_Screen_Cols after the call to SLtt_get_terminfo(). Under Linux, the current terminal size is always available through the TIOCGWINSZ ioctl, which is discussed in detail on page 376.

Initializing the S-Lang’s screen management layer is straightforward:

void SLsmg_init_smg(void);
SLsmg_init_smg()

Updating the Display

Before the results of a sequence of SLsmg routines gets reflected on the physical terminal, you must call the SLsmg_refresh() function. This function does not take any arguments or return a value, but it does update the physical terminal with the results of any screen drawing that has been done since the previous time it was called.

Moving the Cursor

As in most programs, the terminal cursor is used by S-Lang as both the default location for text output and a cue for the user. S-Lang programs can move the cursor with the following code:

extern void SLsmg_gotorc (int row, int column);

Note that the upper-left corner of the screen is (0, 0) and the bottom-right corner is (SLtt_Screen_Rows - 1, SLtt_Screen_Cols - 1).

Finishing Screen Management

When a program that uses SLsmg has finished, it needs to tell S-Lang it is done, allowing S-Lang to free buffers and restore the terminal state. Before doing this, it is a good idea to move the cursor to the bottom of the screen and refresh the display to make sure all output has been shown to the user.

Skeleton Screen Management

Here is a program that initializes S-Lang’s screen management abilities and then closes them. Although it certainly does not do much, it does illustrate the basics of using S-Lang’s SLsmg functionality.

 1: /* slinit.c */
 2:
 3: #include <slang/slang.h>
 4: #include <stdio.h>
 5: #include <sys/ioctl.h>
 6: #include <termios.h>
 7:
 8: int main(void) {
 9:     struct winsize ws;
10:
11:     /* get the size of the terminal connected to stdout */
12:     if (ioctl(1, TIOCGWINSZ, &ws)) {
13:         perror("failed to get window size");
14:         return 1;
15:     }
16:
17:     SLtt_get_terminfo();
18:
19:     SLtt_Screen_Rows = ws.ws_row;
20:     SLtt_Screen_Cols = ws.ws_col;
21:
22:     SLsmg_init_smg();
23:
24:     /* heart of the program goes here */
25:
26:     SLsmg_gotorc(SLtt_Screen_Rows - 1, 0);
27:     SLsmg_refresh();
28:     SLsmg_reset_smg();
29:     SLang_reset_tty();
30:
31:     return 0;
32: }

Switching Character Sets

Most modern terminals (including the VT100, which the Linux console closely emulates) support at least two character sets. The primary one is usually ISO-8859-1 or something similar; the second is used primarily for line-drawing characters. S-Lang allows you to choose which character set is used for drawing characters.

void SLsmg_set_char_set(int useAlternate)

When SLsmg_set_char_set() is called with a nonzero argument, new characters written to the display are mapped through the alternate character set. Calling SLsmg_set_char_set() with zero as its parameter disables this mapping, allowing characters to appear normally.

S-Lang defines a set of symbolic names for the commonly used line-drawing characters contained in the alternate character set. Table 24.1 shows the available line-drawing characters and S-Lang’s name for each.

Table 24.1. Line Characters

Glyph

Symbolic Constant

SLSMG_HLINE_CHAR

SLSMG_VLINE_CHAR

SLSMG_ULCORN_CHAR

SLSMG_URCORN_CHAR

SLSMG_LLCORN_CHAR

SLSMG_LRCORN_CHAR

SLSMG_RTEE_CHAR

SLSMG_LTEE_CHAR

SLSMG_UTEE_CHAR

SLSMG_DTEE_CHAR

SLSMG_PLUS_CHAR

Writing to the Screen

Although there are a number of ways to write strings to the screen under S-Lang, they all look about the same. Here is the entire set of functions:

void SLsmg_write_char(char ch);
void SLsmg_write_string(char * str);
void SLsmg_write_nchars(char * chars, int length);
void SLsmg_write_nstring(char * str, int length);
void SLsmg_printf(char * format, ...);
void SLsmg_vprintf(char * format, va_list args);
void SLsmg_write_wrapped_string(char * str, int row, int column,
                                int height, int width, int fill);

All of these functions, except SLsmg_write_wrapped_string(), write the requested string into the screen buffer[4] at the current cursor location using the current color and character set. They all have different ways of deciding what string to write, however. After the information has been written, the cursor is advanced to the end of the area affected, just as on a normal terminal. Any strings that extend past the right side of the screen are truncated rather than wrapped. Although this is different from normal terminal output, it is reasonable for most full-screen applications, in which wrapped text has an adverse effect on the screen’s layout.

SLsmg_write_char()

Of all the screen output functions, this one is the simplest. It writes the character passed to the current cursor position and advances the cursor.

SLsmg_write_string()

The string that is passed to the function is written to the screen.

SLsmg_write_nchars()

The length characters pointed to by chars are written to the screen. NULL termination is ignored—a '' is written if one is found, and the routine continues past the end of the string.

SLsmg_write_nstring()

At most length characters from str are written to the screen. If str is shorter than length characters, the remainder of the space is filled with blank characters.

SLsmg_printf()

As the name implies, this routine acts like the standard printf() function, formatting the first argument, with the remainder of the arguments used as parameters for the formatting. The formatted string is then written to the screen.

SLsmg_vprintf()

Like the C library’s vfprintf() function, this routine expects a va_arg argument, which it uses to format the first parameter. The formatted string is then displayed.

SLsmg_write_wrapped_string()

Although S-Lang truncates strings rather than wrap them, it does provide a simple function for writing strings wrapped to an arbitrary rectangle on the screen. SLsmg_write_wrapped_string() writes str to the rectangle that begins at row and column of size height and width.

Although this routine does wrap on word boundaries, a in the string forces it to go to the next line. If final parameter fill is nonzero, every line is filled to the full width of the rectangle, with spaces used for padding where necessary.

Drawing Lines and Boxes

Although SLsmg_set_char_set() provides all the functionality needed to draw simple line graphics on a terminal, S-Lang provides some shortcut functions that are easier to use.

void SLsmg_draw_hline(int row);
void SLsmg_draw_vline(int column);
void SLsmg_draw_box(int row, int column, int height, int width);

The SLsmg_draw_hline() function draws a single horizontal line at row row; SLsmg_draw_vline() draws a single vertical line at column col.

SLsmg_draw_box() draws a box starting at row and col that extends for height rows and width columns. SLsmg_draw_box() is similar to a combination of SLsmg_draw_hline() and SLsmg_draw_vline(), but it gets the corners right, as well.

Here is an example program that draws a screen containing the normal character set and the alternate character set. It also demonstrates a simple use of SLsmg_draw_box().

 1: /* slcharset.c */
 2:
 3: #include <slang/slang.h>
 4: #include <stdio.h>
 5: #include <sys/ioctl.h>
 6: #include <termios.h>
 7:
 8: /* displays a table containing 256 characters in a single character
 9:    set, starting a column col. The 'label' is displayed over the
10:    table, and the alternate character set is displayed iff
11:    isAlternate is nonzero */
12: static void drawCharSet(int col, int isAlternate, char * label) {
13:     int i, j;
14:     int n = 0;
15:
16:     /* draw the box */
17:     SLsmg_draw_box(0, col, 20, 38);
18:
19:     /* center the label */
20:     SLsmg_gotorc(0, col + 2);
21:     SLsmg_write_string(label);
22:
23:
24:     /* draw the horizontal legend */
25:     SLsmg_gotorc(2, col + 4);
26:     SLsmg_write_string("0 1 2 3 4 5 6 7 8 9 A B C D E F");
27:
28:     /* set the character set to use */
29:     SLsmg_set_char_set(isAlternate);
30:
31:     /* this iterates over the 4 most significant bits */
32:     for (i = 0; i < 16; i++) {
33:         SLsmg_gotorc(3 + i, 2 + col);
34:         SLsmg_write_char(i < 10 ? i + '0': (i - 10) + 'A'),
35:
36:         /* this iterates over the 4 least significant bits */
37:         for (j = 0; j < 16; j++) {
38:             SLsmg_gotorc(3 + i, col + 4 + (j * 2));
39:             SLsmg_write_char(n++);
40:         }
41:     }
42:
43:     SLsmg_set_char_set(0);
44: }
45:
46: int main(void) {
47:     struct winsize ws;
48:
49:     /* get the size of the terminal connected to stdout */
50:     if (ioctl(1, TIOCGWINSZ, &ws)) {
51:         perror("failed to get window size");
52:         return 1;
53:     }
54:
55:     SLtt_get_terminfo();
56:
57:     SLtt_Screen_Rows = ws.ws_row;
58:     SLtt_Screen_Cols = ws.ws_col;
59:
60:     SLsmg_init_smg();
61:     SLang_init_tty(-1, 0, 1);
62:
63:     drawCharSet(0, 0, "Normal Character Set");
64:     drawCharSet(40, 1, "Alternate Character Set");
65:
66:     SLsmg_refresh();
67:     SLang_getkey();
68:
69:     SLsmg_gotorc(SLtt_Screen_Rows - 1, 0);
70:     SLsmg_refresh();
71:     SLsmg_reset_smg();
72:     SLang_reset_tty();
73:
74:     return 0;
75: }

Using Color

S-Lang makes it easy to add color to an application. It allows the user to use a palette of 256 entries[5], each defining a foreground and a background color. Most applications use a palette entry for one type of rendered object, such as window frame or listbox entry. A palette’s colors are set through SLtt_set_color().

void SLtt_set_color(int entry, char * name, char * fg, char * bg);

The first parameter specifies the palette entry being modified. The name parameter is currently ignored and should be passed as NULL. The final two entries name the new foreground and background colors for that palette entry. Table 24.2 lists the colors S-Lang supports; the fg and bg should both be strings containing the name of the color to use. All the colors on the left side of the table may be used for the foreground or background color. However, the colors on the right side must be used only for foreground colors. Using them for background colors would give unpredictable results.[6]

Table 24.2. S-Lang Colors

Foreground or Background

Foreground

black

gray

red

brightred

green

brightgreen

brown

yellow

blue

brightblue

magenta

brightmagenta

cyan

brightcyan

lightgray

white

Writes to the screen are done using the current palette entry, which is set by the SLsmg_set_color() function.

void SLsmg_set_color(int entry);

This sets the current palette entry to the specified entry. The colors specified by this entry are used for future screen writes.

Although an application may use the color-related functions on any terminal type, a number of factors control whether colors are displayed. The SLtt_Use_Ansi_Colors global variable controls whether colors are displayed. If it is set to zero, no colors are used. Any other value allows colors to be displayed.

SLtt_get_terminfo() tries to guess whether color should be enabled on the current terminal. Unfortunately, many termcap and terminfo databases are incomplete in this regard. If the COLORTERM environment variable is set, S-Lang sets SLtt_Use_Ansi_Colors no matter what the terminal database indicates.

Most applications that provide color support also provide a command-line option to allow users to selectively enable color support. When the option is used, the application explicitly sets SLtt_Use_Ansi_Colors.



[1] One of the authors of this book has written a higher-level windowing toolkit based on S-Lang called newt, which is included with most Linux distributions.

[2] As described by the terminfo database.

[3] An error occurs if a signal is received while S-Lang is waiting for a keystroke.

[4] Remember, the physical terminal is updated only by SLsmg_refresh().

[5] This number could be increased in the future, but it is doubtful such a change will ever be necessary.

[6] Results might include blinking text on some systems.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset