Terminal programming

Created 2022-10-20 Updated 2023-04-03

Raw, unblocked, stdin

/*
 * 2023-03-31   Started
 */
#include <ctype.h>
#include <fcntl.h>
#include <stdio.h>
#include <sys/poll.h>
#include <sys/time.h>
#include <unistd.h>
#include <stdexcept>
#include <termios.h>

static struct termios term_ori; // original term that we want to restore when we've finished

void term_init(void)
{
        // https://linux.die.net/man/3/termios
        struct termios term;
        if(tcgetattr(STDIN_FILENO, &term) < 0) exit(1); // what are the current attributes?
        term_ori = term; // creat a copy for later restoration
        cfmakeraw(&term); // raw mode; char-by-char, echo disabled, special chars disabled
        // cfmakeraw() does too much, so we have to set things back again
        term.c_iflag |= ICRNL; // translate CR to NL on input
        term.c_oflag |= OPOST; // get newlines working properly again on output
        term.c_lflag |= ISIG; // re-enable INTR, QUIT, SUSP, DSUPS. Ctl-C will work again.
        if(tcsetattr(STDIN_FILENO, TCSANOW, &term)) exit(1); // set the attributes how we wish
}

void term_deinit(void)
{
        if(tcsetattr(STDIN_FILENO, TCSANOW, &term_ori)) exit(1);
}

/*
 * NB stdout will be supressed unless there is a \n for an fflush(stdout)
 */
int get_key_non_blocking(void) 
{
        int fd;
        char c;
        const nfds_t nfds = 1;
        struct pollfd pfds[nfds];
        int timeout = 1; // ms
        const int stdin_fd = STDIN_FILENO;
        pfds[0].fd = stdin_fd; // stdin
        pfds[0].events = POLLIN | POLLHUP;
        //while(1) {
                int n = poll(pfds, nfds, timeout);
                if(n==0) return -1;
                if(pfds[0].revents & POLLHUP) {
                        return EOF;
                }
                if(pfds[0].revents & POLLIN) {
                        int i = read(stdin_fd, &c, 1);
                        return c;
                        //putchar(toupper(c));
                        //printf(" %d, ", c);
                        //fflush(stdout);
                }
        //}
                return -1;
}

int main()
{
        term_init();
        while(1) {
                char c = get_key_non_blocking();
                if(c == -1) continue;
                if(c == 'x') break;
                putchar(c);
                fflush(stdout);
                usleep(1000);
        }
        term_deinit();
        return 0;
}


Raw, blocked, stdin

This program demonstrates the use of

See also: man tty_ioctl

It stops character echoing on the terminal. Newlines are still echoed properly. Any character that the user inputs is printed out in uppercase. `poll()` is used to check to see if a char is available, rather than using `getchar()`.

/* test polling of input of stdin and writes the input in upper case.
 *
 * Only need to do this prior to 2022-10-21:
 * --- Begin ---
 * Run:
 * 	stty -echo -icanon time 0 min 0 && ./poll
 * Afterwards, return tty to sane state:
 * 	stty sane
 * --- End ---
 *
 * 2022-12-02	Added some modularity
 * 2022-10-21	Controlling how input and output works via termios
 * 2022-07-21	created. Works
 */

#include <ctype.h>
#include <fcntl.h>
#include <stdio.h>
#include <sys/poll.h>
#include <sys/time.h>
#include <unistd.h>
#include <stdexcept>
#include <termios.h>

static struct termios term_ori; // original term that we want to restore when we've finished

void term_init(void)
{
	// https://linux.die.net/man/3/termios
	struct termios term;
	if(tcgetattr(STDIN_FILENO, &term) < 0) exit(1); // what are the current attributes?
	term_ori = term; // creat a copy for later restoration
	cfmakeraw(&term); // raw mode; char-by-char, echo disabled, special chars disabled
	// cfmakeraw() does too much, so we have to set things back again
	term.c_iflag |= ICRNL; // translate CR to NL on input
	term.c_oflag |= OPOST; // get newlines working properly again on output
	term.c_lflag |= ISIG; // re-enable INTR, QUIT, SUSP, DSUPS. Ctl-C will work again.
	if(tcsetattr(STDIN_FILENO, TCSANOW, &term)) exit(1); // set the attributes how we wish
}

void term_deinit(void)
{
	if(tcsetattr(STDIN_FILENO, TCSANOW, &term_ori)) exit(1);
}

/*
 * NB stdout will be supressed unless there is a \n for an fflush(stdout)
 */
int get_key_blocking(void)
{
	int fd;
	char c;
	const nfds_t nfds = 1;
	struct pollfd pfds[nfds];
	int timeout = 1; // ms
	const int stdin_fd = STDIN_FILENO;
	pfds[0].fd = stdin_fd; // stdin
	pfds[0].events = POLLIN | POLLHUP;
	while(1) {
		int n = poll(pfds, nfds, timeout);
		if(n==0) continue;
		if(pfds[0].revents & POLLHUP) {
			return EOF;
		}
		if(pfds[0].revents & POLLIN) {
			int i = read(stdin_fd, &c, 1);
			return c;
			//putchar(toupper(c));
			//printf(" %d, ", c);
			//fflush(stdout);
		}
	}
}

int main() 
{
	term_init();
	puts("type 'q' to quit. I will echo output in uppercase");


	while(1) {
		//putchar('.');
		int c= get_key_blocking();
		if(c == EOF) {
			printf("\nEOF detected\n");
			break;
		}
		if(c == 'q') break;
		putchar(toupper(c));
		fflush(stdout); // important!
	}


	term_deinit();
	puts("Bye now");
	return 0;
}

Source

Raw, blocked, tty

This example takes input from the tty in non-coocked mode, so it doesn't require the user to press Return before responding. The following example converts your input key to uppercase. It blocks on input, and echos the input key to output. You can, optionally, use stdin from file redirection (as per note 1).

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <ctype.h>

#include <termios.h>

int main(void)
{
        struct termios term, term_ori;
        int fd = open("/dev/tty", O_RDONLY);
        if(tcgetattr(fd, &term) < 0) exit(1); // what are the current attributes?
        term_ori = term; // create a copy for later restoration
        term.c_lflag &= ~ICANON; // don't wait for newline
        if(tcsetattr(fd, TCSANOW, &term)) exit(1); // set the attributes how we wish

	// see note 1

        int c;
        while(n = read(fd, &c, 1)) {
                if(c == 'q') break;
                c = toupper(c);
                write(STDOUT_FILENO, &c, 1);
        }
        close(fd);

        if(tcsetattr(STDIN_FILENO, TCSANOW, &term_ori)) exit(1);
        return 0;
}

Note 1

You can also additionally use stdin:

        int n;
        char buf[101];
        while(n = fread(buf, 1, 100, stdin)) {
                if(n==0) break;
                buf[n] = 0;
                printf("%s", buf);
        }
        puts("OK, I've finished echoing stdin, now type something. q to quit");

So you can do:

a.out < myfile.txt

and it will print the file to stdout, and accept input from the tty.

Getting the size of a terminal

/*
 * get the size of the terminal
 *
 * see also TIOCSWINSZ for setting terminal size
 *
 * 2022-12-04	Started
 */

#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <ctype.h>
#include <termios.h>


void say(char *src, unsigned short val)  {printf("%s\t%d\n", src, val); }

int main()
{
	int fd = open("/dev/tty", O_RDONLY);

	struct winsize wz;
	int ret = ioctl(fd, TIOCGWINSZ, &ws);
	if(ret==-1) { perror(strerror(errno)); }
	say("num rows:", 	ws.ws_row);
	say("num cols:", 	ws.ws_col);
	say("num xpixels:", 	ws.ws_xpixel);
	say("num ypixels:", 	ws.ws_ypixel);


	close(fd);
	return 0;
}

Typical output:

num rows:	24
num cols:	80
num xpixels:	0
num ypixels:	0

Gitlab source

Keycode recognition

The following code shows raw key codes for terminal input keys:

/*
 * show raw key codes of terminal keys input
 *
 * 2022-12-05   Started. Works
 */

#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <ctype.h>
#include <termios.h>
#include <stdlib.h>


int main()
{
        struct termios term, term_ori;
        int fd = open("/dev/tty", O_RDONLY);
        if(tcgetattr(fd, &term) < 0) exit(1); // what are the current attributes?
        term_ori = term; // create a copy for later restoration
        term.c_lflag &= ~ICANON; // don't wait for newline
        if(tcsetattr(fd, TCSANOW, &term)) exit(1); // set the attributes how we wish


        char buf[6];
        int n;
        while(n = read(fd, &buf, sizeof(buf))) {
                printf("\t");
                for(int i = 0; i< sizeof(buf); i++) {
                        printf(" %3d", buf[i]);
                }
                puts("");
                if(buf[0] == 'q') break;
        }

        close(fd);
        if(tcsetattr(STDIN_FILENO, TCSANOW, &term_ori)) exit(1);
        return 0;
}

Example output:

^[[6~	  27  91  54 126   0   0

This is the result of pressing the Page Down Key. ESC is decimal 27 (\033), 91 is '[', 54 is '6', and 126 is '~'.

Source code

SERIAL PORT

#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <ctype.h>
#include <termios.h>
#include <stdlib.h>

int main()
{
        struct termios tio0, tio;

        const char device[] = "/dev/ttyACM0";
        int fd = open(device, O_RDWR | O_NOCTTY | O_NDELAY);
        if(fd == -1) {
                printf( "failed to open port\n" );
        }

        tcgetattr(fd, &tio0);
        tio = tio0; 
        tio.c_lflag &= ~ICANON; // disable canonical mode
        cfsetispeed(&tio, B115200);
        cfsetospeed(&tio, B115200);
        tcsetattr(fd, TCSANOW, &tio);

        for(int i = 0 ; i < 10; i++) {
                char c = 1;
                write(fd, &c, 1);
                usleep(500'000);
        }
        close(fd);
}

Source

ANSI

0: Reset style

1: Bold

2: light

3: italics

4: underlined

30: Black

31: Red

32: Green

33: Yellow

34: Blue

35: Magenta

36: Cyan

37: White

Usage: echo -e "\033[31mI am red"

Combining multiple:

Use ';' to separate elements

Use 'm' to finish sequence

E.g.: \033[31;1m will be bold red

\033[A up arrow

\033[B down arrow

\033[C right arrow

\033[D left arrow

\033[F end

\033[H home

\033[J clear screen

\033[2~ insert

\033[3~ delete

\033[5~ page up

\033[6~ page down

Cursor

printf("\033[?25l"); // cursor off
printf("\033[?25h"); // cursor on

EXITS

termios(3) - terminal interface functions