Any way to handle the escape key in canonical mode?

In Unix plain C termios software, if I use canonical mode to get an input string from a user, how can I handle the jump key? In general, if a user enters a line of text and presses the escape button, nothing happens. I would like to cancel the current input if the user presses the escape button. I know that I can process individual characters, but then I lose all the advantages of the canonical mode (back spaces, etc.).

+5
source share
3 answers

Original

With all the loans, Jonathan Leffler for the comment that hinted me in the right direction , below is my annotated first termios program demonstrator (Thanks!).

The key is to use tcgetattr(ttyfd, &attributes) in the current terminal file descriptor to retrieve its current attributes in struct termios , edit the attributes, and then apply the changes using tcsetattr(ttyfd, when, &attributes) .

One attribute is the "kill" character - a character that causes the entire current buffered string to be discarded. It is set by indexing the array of c_cc elements from struct termios and setting attr.c_cc[VKILL] to anything (here, on Esc , which is equal to octal 033 ).

When exiting, the return character must be restored to its previous value.

 #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 seems to work on Mac OS X 10.6.8. I also tested this on Linux, and apparently Esc to kill the buffer, by default, as if I were printing c_cc[VKILL] , I get 27 == 033 == ESC .

Edit

In the attempts below to maximize the behavior described in your comment. It sets c_cc[VEOL2] to Esc ; EOL2 is the alternate end of the line. It also removes Esc as the kill character, since you want to get the string.

Now it happens that if normal Ret is pressed, everything is fine. However, if you press Esc , the last character in the buffer will be set to Esc , a condition that can be checked (although only after reading and buffering the entire line in the first place).

Below is a demonstrator according to your specified specifications. It waits for an input line and repeats it back with

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

Enjoy it!

 #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; } 
+4
source

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

+2
source

This is a slightly modified version of my getLine() function, used for reliable user input. Here you can see the details of the original, but this one has been modified to use termios material, which allows you to control the degree of input.

Since termios operates at a lower level than standard C input, this also affects this.

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 

Further, an auxiliary function for returning the terminal to its original state, this makes it easy to return a value from getLine() , knowing that the terminal will remain 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 getLine() , which modifies the terminal's attributes to make the ESC kill character, then calls fgets() along with all the additional functions to request, detect buffer overflows, enter a flush at the end of the line, etc.

During the time that the user is within fgets() as part of this function, the changed behavior of the terminal 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 you can test its behavior. Basically, this will allow you to enter lines of up to twenty characters, then prints them with 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 exit the program.

 // 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; } 
+2
source

Source: https://habr.com/ru/post/1204604/


All Articles