Any way to process escape key in canonical mode? Any way to process escape key in canonical mode? unix unix

Any way to process escape key in canonical mode?


Original

With all due credits to Jonathan Leffler for his comment that hinted me at the right direction, at the bottom is my annotated first termios program demonstrator (Thanks!).

The key is to use tcgetattr(ttyfd, &attributes) on the current terminal's file descriptor to retrieve its current attributes into in a struct termios, edit the attributes, then apply the changes with tcsetattr(ttyfd, when, &attributes).

One of the attributes is the "kill" character - the character that causes the entire currently-buffered line to be discarded. It is set by indexing into the c_cc member array of struct termios and setting attr.c_cc[VKILL] to whatever one wants (Here, to Esc, which is equal to octal 033).

The kill character should be restored to its previous value on exit.

#include <termios.h>#include <fcntl.h>#include <stdio.h>int main(){    char   buf[80];    int    numBytes;    struct termios original, tattered;    int    ttyfd;        /* Open the controlling terminal. */    ttyfd = open("/dev/tty", O_RDWR);    if(ttyfd < 0){        printf("Could not open tty!\n");        return -1;    }        /**     * Get current terminal properties, save them (including current KILL char),     * set the new KILL char, and make this the new setting.     */        tcgetattr(ttyfd, &original);    tattered = original;    tattered.c_cc[VKILL] = 033;/* New killchar, 033 == ESC. */    tcsetattr(ttyfd, TCSANOW, &tattered);        /**     * Simple test to see whether it works.     */        write(1, "Please enter a line: ", 21);    numBytes = read(0, buf, sizeof buf);    write(1, buf, numBytes);        /**     * Restore original settings.     */        tcsetattr(ttyfd, TCSANOW, &original);         /* Clean up. */    close(ttyfd);        return 0;}

This demo appears to work on Mac OS X 10.6.8. I've also tested this on Linux, and apparently Esc to kill the buffer appears to be the default, as if I print out c_cc[VKILL] I obtain 27 == 033 == ESC.

Edit

The below attempts as closely as possible to imitate the behaviour you described in your comment. It sets c_cc[VEOL2] to Esc; EOL2 is the alternate End-of-Line. It also removes Esc as the kill character, since you want to receive the line.

What now happens is that if a normal Ret is pressed, all is normal. However, if Esc is pressed, the last character in the buffer is set to Esc, a condition which may be tested (although only after reading and buffering the whole line first).

Below is a demonstrator according to your clarified specs. It waits for a line of input and echoes it back with

  • <CANCELLED> if the line was terminated with Esc and
  • <NORMAL > if the line was terminated with Ret.

Enjoy!

#include <termios.h>#include <fcntl.h>#include <stdio.h>int main(){    char   buf[80];    int    numBytes;    struct termios original, tattered;    int    ttyfd;    /* Open the controlling terminal. */    ttyfd = open("/dev/tty", O_RDWR);    if(ttyfd < 0){        printf("Could not open tty!\n");        return -1;    }    /**     * Get current terminal properties, save them (including current KILL char),     * set the new KILL char, and make this the new setting.     */    tcgetattr(ttyfd, &original);    tattered = original;    tattered.c_cc[VKILL] = 0;  /* <Nada> */    tattered.c_cc[VEOL2] = 033;/* Esc */    tcsetattr(ttyfd, TCSANOW, &tattered);    /**     * Simple test to see whether it works.     */    fputs("Please enter a line: ", stdout);    fflush(stdout);    numBytes = read(0, buf, sizeof buf);    if(buf[numBytes-1]==033){/* Last character is Esc? */        buf[numBytes-1] = '\n';/* Substitute with newline */        fputs("\n<CANCELLED> ", stdout);   /* Print newline to move to next line */    }else{        fputs("<NORMAL   > ", stdout);    }    fwrite(buf, 1, numBytes, stdout);    /**     * Restore original settings.     */    tcsetattr(ttyfd, TCSANOW, &original);    /* Clean up. */    close(ttyfd);    return 0;}


You need to set the EOF character to ESC instead of Enter using the tcsetattr() function. For more detailed info visit http://pubs.opengroup.org/onlinepubs/7908799/xbd/termios.html#tag_008_001_009


This is a slightly modified version of my getLine() function, used for robust input from the user. You can see details on the original here but this one has been modified to use the termios stuff which allows a degree of control over input.

Because termios works at a lower level than standard C input, it affects that as well.

First, the required headers and return values from the getLine() function:

#include <termios.h>#include <fcntl.h>#include <stdio.h>#include <string.h>#define OK        0#define NO_INPUT  1#define TOO_LONG  2#define TERM_PROB 3

Next, a helper function for reverting the terminal to its original state, this allows you to easily return a value from getLine() knowing that the terminal will be left in its original state.

static int revertTerm (int fd, struct termios *ptio, int old, int rc) {    // Revert the terminal to its original state then return    // specified value.    ptio->c_cc[VKILL] = old;    tcsetattr (fd, TCSANOW, ptio);    close (fd);    return rc;}

Next, the actual getLine() function itself, which modifies the terminal attributes to make ESC the kill character, then calls fgets() along with all the extras for prompting, detecting buffer overflow, flushing input to the end of the line and so on.

During the time that user is within fgets() as part of this function, the modified terminal behaviour is active and you can use ESC to clear the line.

static int getLine (char *prmpt, char *buff, size_t sz) {    int old, fd, ch, extra;    struct termios tio;    // Modify teminal so ESC is KILL character.    fd = open ("/dev/tty", O_RDWR);    if (fd < 0)        return TERM_PROB;    tcgetattr (fd, &tio);    old = tio.c_cc[VKILL];    tio.c_cc[VKILL] = 0x1b;    tcsetattr (fd, TCSANOW, &tio);    // Get line with buffer overrun protection.    if (prmpt != NULL) {        printf ("%s", prmpt);        fflush (stdout);    }    if (fgets (buff, sz, stdin) == NULL)        return revertTerm (fd, &tio, old, NO_INPUT);    // If it was too long, there'll be no newline. In that case, we flush    // to end of line so that excess doesn't affect the next call.    if (buff[strlen(buff)-1] != '\n') {        extra = 0;        while (((ch = getchar()) != '\n') && (ch != EOF))            extra = 1;        return revertTerm (fd, &tio, old, (extra == 1) ? TOO_LONG : OK);    }    // Otherwise remove newline and give string back to caller.    buff[strlen(buff)-1] = '\0';    return revertTerm (fd, &tio, old, OK);}

And finally, a test program so that you can check its behaviour. Basically, it will allow you to enter lines up to twenty characters then it will print them out with a status (too long, no input, etc).

If, at any time during the input process, you press ESC, it will kill the line and start again.

Entering exit will cause the program to exit.

// Test program for getLine().int main (void) {    int rc, done = 0;    char buff[21];    while (!done) {        rc = getLine ("Enter string (ESC to clear, exit to stop)> ",            buff, sizeof(buff));        if (rc == NO_INPUT) {            // Extra NL since my system doesn't output that on EOF.            printf ("\nNo input\n");        } else if (rc == TOO_LONG) {            printf ("Input too long [%s]\n", buff);        } else {            done = (strcmp (buff, "exit") == 0);            if (!done)                printf ("OK [%s]\n", buff);        }    }    return 0;}