Thread: Termios examples?

  1. #1
    Registered User
    Join Date
    Feb 2013
    Location
    Sweden
    Posts
    89

    Cool Termios examples?

    I'm trying to learn how to control user input better (in C) and was advised to take a look at termios. I found the man pages and a few examples, and I would just appreciate more examples featuring termios. I have already found examples how set no echo and unbuffered input, but I guess I could do more things with it (regarding input from a keyboard and maybe how to control the cursor in different ways).

    My native language is not English, which makes those man pages a little bit more difficult to understand, so a couple of nice examples would really be useful to me…


  2. #2
    Registered User
    Join Date
    May 2010
    Posts
    4,632
    What are you actually trying to do? Are you sure you need to play with termios? Perhaps using curses would be a better choice?

    Edit: Okay, it would have been nice if you would have referenced the post about ncurses in the C forum.

    You may want to try searching for things like getch() for linux. You should be able to find some examples, for example this one. Also just searching for termios should get you many many different examples of how to use the termios. For example. Most examples will probably be talking about modifiying a serial terminal, but in Linux the console is just a version of a serial terminal.

    Jim
    Last edited by jimblumberg; 08-04-2013 at 11:00 AM.

  3. #3
    Registered User
    Join Date
    Feb 2013
    Location
    Sweden
    Posts
    89
    Quote Originally Posted by jimblumberg View Post
    Okay, it would have been nice if you would have referenced the post about ncurses in the C forum.
    Yes, you're right. Sorry for that.

    Thanks for replying.
    I'll check those links out right away.

  4. #4
    Ticked and off
    Join Date
    Oct 2011
    Location
    La-la land
    Posts
    1,728
    Here is a minimal example, that should be compatible with standard C I/O.

    The compatibility is based on the fact that this does not change terminal settings in a way that would change how the low-level read()/write() functions behave.

    While it is possible that a C library will get confused by these changed, such a C library will not be able to handle all possible input from normal terminals either; only from a small subset of terminals specifically in canonical mode. In particular, the writers of such a C library must have made a bunch of stupid assumptions, which are likely to bite an unwary programmer in fascinatingly weird and annoying ways.

    In other words, you can expect any normal C library implementation to work with this. (It has worked on all C libraries I have access to, that do support the termios interface.)

    minimal.h:
    Code:
    #include <unistd.h>
    #include <termios.h>
    #include <signal.h>
    #include <stdio.h>
    
    static int            terminal_descriptor = -1;
    static struct termios terminal_original;
    static struct termios terminal_settings;
    
    /* Restore terminal to original settings
    */
    static void terminal_done(void)
    {
        if (terminal_descriptor != -1)
            tcsetattr(terminal_descriptor, TCSANOW, &terminal_original);
    }
    
    /* "Default" signal handler: restore terminal, then exit.
    */
    static void terminal_signal(int signum)
    {
        if (terminal_descriptor != -1)
            tcsetattr(terminal_descriptor, TCSANOW, &terminal_original);
    
        /* exit() is not async-signal safe, but _exit() is.
         * Use the common idiom of 128 + signal number for signal exits.
         * Alternative approach is to reset the signal to default handler,
         * and immediately raise() it. */
        _exit(128 + signum);
    }
    
    /* Initialize terminal for non-canonical, non-echo mode,
     * that should be compatible with standard C I/O.
     * Returns 0 if success, nonzero errno otherwise.
    */
    static int terminal_init(void)
    {
        struct sigaction act;
    
        /* Already initialized? */
        if (terminal_descriptor != -1)
            return errno = 0;
    
        /* Which standard stream is connected to our TTY? */
        if (isatty(STDERR_FILENO))
            terminal_descriptor = STDERR_FILENO;
        else
        if (isatty(STDIN_FILENO))
            terminal_descriptor = STDIN_FILENO;
        else
        if (isatty(STDOUT_FILENO))
            terminal_descriptor = STDOUT_FILENO;
        else
            return errno = ENOTTY;
    
        /* Obtain terminal settings. */
        if (tcgetattr(terminal_descriptor, &terminal_original) ||
            tcgetattr(terminal_descriptor, &terminal_settings))
            return errno = ENOTSUP;
    
        /* Disable buffering for terminal streams. */
        if (isatty(STDIN_FILENO))
            setvbuf(stdin, NULL, _IONBF, 0);
        if (isatty(STDOUT_FILENO))
            setvbuf(stdout, NULL, _IONBF, 0);
        if (isatty(STDERR_FILENO))
            setvbuf(stderr, NULL, _IONBF, 0);
    
        /* At exit() or return from main(),
         * restore the original settings. */
        if (atexit(terminal_done))
            return errno = ENOTSUP;
    
        /* Set new "default" handlers for typical signals,
         * so that if this process is killed by a signal,
         * the terminal settings will still be restored first. */
        sigemptyset(&act.sa_mask);
        act.sa_handler = terminal_signal;
        act.sa_flags = 0;
        if (sigaction(SIGHUP,  &act, NULL) ||
            sigaction(SIGINT,  &act, NULL) ||
            sigaction(SIGQUIT, &act, NULL) ||
            sigaction(SIGTERM, &act, NULL) ||
    #ifdef SIGXCPU
            sigaction(SIGXCPU, &act, NULL) ||
    #endif
    #ifdef SIGXFSZ    
            sigaction(SIGXFSZ, &act, NULL) ||
    #endif
    #ifdef SIGIO
            sigaction(SIGIO,   &act, NULL) ||
    #endif
            sigaction(SIGPIPE, &act, NULL) ||
            sigaction(SIGALRM, &act, NULL))
            return errno = ENOTSUP;
    
        /* Let BREAK cause a SIGINT in input. */
        terminal_settings.c_iflag &= ~IGNBRK;
        terminal_settings.c_iflag |=  BRKINT;
    
        /* Ignore framing and parity errors in input. */
        terminal_settings.c_iflag |=  IGNPAR;
        terminal_settings.c_iflag &= ~PARMRK;
    
        /* Do not strip eighth bit on input. */
        terminal_settings.c_iflag &= ~ISTRIP;
    
        /* Do not do newline translation on input. */
        terminal_settings.c_iflag &= ~(INLCR | IGNCR | ICRNL);
    
    #ifdef IUCLC
        /* Do not do uppercase-to-lowercase mapping on input. */
        terminal_settings.c_iflag &= ~IUCLC;
    #endif
    
        /* Use 8-bit characters. This too may affect standard streams,
         * but any sane C library can deal with 8-bit characters. */
        terminal_settings.c_cflag &= ~CSIZE;
        terminal_settings.c_cflag |=  CS8;
    
        /* Enable receiver. */
        terminal_settings.c_cflag |=  CREAD;
    
        /* Let INTR/QUIT/SUSP/DSUSP generate the corresponding signals. */
        terminal_settings.c_lflag |=  ISIG;
    
        /* Enable noncanonical mode.
         * This is the most important bit, as it disables line buffering etc. */
        terminal_settings.c_lflag &= ~ICANON;
    
        /* Disable echoing input characters. */
        terminal_settings.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL);
    
        /* Disable implementation-defined input processing. */
        terminal_settings.c_lflag &= ~IEXTEN;
    
        /* To maintain best compatibility with normal behaviour of terminals,
         * we set TIME=0 and MAX=1 in noncanonical mode. This means that
         * read() will block until at least one byte is available. */
        terminal_settings.c_cc[VTIME] = 0;
        terminal_settings.c_cc[VMIN] = 1;
    
        /* Set the new terminal settings.
         * Note that we don't actually check which ones were successfully
         * set and which not, because there isn't much we can do about it. */
        tcsetattr(terminal_descriptor, TCSANOW, &terminal_settings);
    
        /* Done. */
        return errno = 0;
    }
    When terminal_init() is called, the terminal will be set into noncanonical mode (no line buffering etc.), echo (displaying characters the user types) will be disabled, and related standard C streams set to non-buffered mode.

    Normal letters will stay unchanged in the input, but special keys generate either an ASCII code (Ctrl+A = 1, Ctrl+B = 1, and so on), or an ANSI escape sequence. For example, the up cursor key will generate three characters: '\033', '[', 'A'. It is of course the ANSI escape sequence for "one row up", "\033[A".

    Here is a simple example program, which shows you the codes keypresses generate. It #include's the above "minimal.h".
    Code:
    #include <stdlib.h>
    #include <stdio.h>
    #include <string.h>
    #include <errno.h>
    #include "minimal.h"
    
    int main(void)
    {
        int c;
    
        if (terminal_init()) {
            if (errno == ENOTTY)
                fprintf(stderr, "This program requires a terminal.\n");
            else
                fprintf(stderr, "Cannot initialize terminal: %s.\n", strerror(errno));
            return EXIT_FAILURE;
        }
    
        printf("Press CTRL+C or Q to quit.\n");
    
        while ((c = getc(stdin)) != EOF) {
            if (c >= 33 && c <= 126)
                printf("0x%02x = 0%03o = %3d = '%c'\n", c, c, c, c);
            else
                printf("0x%02x = 0%03o = %3d\n", c, c, c);
    
            if (c == 3 || c == 'Q' || c == 'q')
                break;
        }
    
        printf("Done.\n");
    
        return EXIT_SUCCESS;
    }


    Now, if you do not need compatibility with standard C I/O, and intend to use the entire terminal screen/window anyway, you should use ncurses or some other Curses library.

    However, if you wanted to write a command-line application that is used to e.g. read the user password, something like the above is a good choice. (In particular, the first refresh operation after a Curses initscr() call will clear the screen, and that is usually unwanted in a straightforward command-line application.)

    Truly portable code can avoid any issues the standard C I/O implementation may have, by instead using custom low-level I/O functions instead. In particular, a subset of functions provided by curses libraries would definitely be doable. (This is what GNU readline library does, for example.)

    If there is interest, I could show some example code for that too. In particular, it might be useful to have non-blocking input (that parses ANSI escape sequences into a single int on input), and some limited curses-like functions; plus of course a normal printf() function to output to standard output and/or error (regardless of whether they are the terminal or something else).

  5. #5
    Registered User
    Join Date
    Feb 2013
    Location
    Sweden
    Posts
    89
    Quote Originally Posted by Nominal Animal View Post
    Truly portable code can avoid any issues the standard C I/O implementation may have, by instead using custom low-level I/O functions instead. In particular, a subset of functions provided by curses libraries would definitely be doable. (This is what GNU readline library does, for example.)

    If there is interest, I could show some example code for that too. In particular, it might be useful to have non-blocking input (that parses ANSI escape sequences into a single int on input), and some limited curses-like functions; plus of course a normal printf() function to output to standard output and/or error (regardless of whether they are the terminal or something else).
    I find all this very interesting indeed, so yes, I'd like to see examples of that too.

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. serial programming using termios
    By xubin in forum Linux Programming
    Replies: 0
    Last Post: 03-17-2013, 01:02 AM
  2. termios.h: B0 => "Hang up"...what does this mean
    By nicoeschpiko in forum C Programming
    Replies: 1
    Last Post: 03-08-2010, 11:26 PM
  3. Termios question
    By thesourcehim in forum Linux Programming
    Replies: 1
    Last Post: 07-09-2008, 03:27 AM
  4. Problem with Termios and a tty
    By acpi in forum C Programming
    Replies: 1
    Last Post: 08-27-2007, 11:41 PM
  5. Termios Flags Rs232 Serial Ttys0 Modbus Rtu
    By Mighty-D in forum Linux Programming
    Replies: 2
    Last Post: 10-31-2006, 11:53 AM

Tags for this Thread