How to get around no backspace when ICANON in non-canonical - c

I am using termios as suggested in a previous question I asked but now am asking if there is a way get backspace to work whilst using termios in non-canonical mode. I am using termios to have not have an echo If I use &=ECHO and &=ICANON this is the result I want, the keyboard input is sent to putchar() as soon as the key is press and displayed but the '\b' key is display as hex, if I do the opposite I can't see the text till enter is pressed but '\b' works.
I have looked up the manual and some other forums that and they said " not possible just don't make any mistakes", this would make sense seeing as how when I don't enter my password correctly in in a terminal on Ubuntu I can't backspace and change it. But I was making sure I haven't missed anything in the manual.
Code is to get input from stdin and not display empty lines.
#include <unistd.h>
#include <termios.h>
#include <errno.h>
#include <stdio.h>
#define ECHOFLAGS (ECHO)
int setecho(int fd, int onflag);
int first_line(int *ptrc);
int main(void){
struct termios old;
tcgetattr(STDIN_FILENO,&old);
setecho(STDIN_FILENO,0);
int c;
while((c = getchar())!= 4) //no end of file in non-canionical match to control D
first_line(&c);
tcsetattr(STDIN_FILENO,&old);
return 0;
}
int setecho(int fd, int onflag){
int error;
struct termios term;
if(tcgetattr(fd, &term) == -1)
return -1;
if(onflag){ printf("onflag\n");
term.c_lflag &= ECHOFLAGS ; // I know the onflag is always set to 0 just
term.c_lflag &=ICANON; // testing at this point
}
else{ printf("else\n");
term.c_lflag &= ECHO;
term.c_lflag &=ICANON;
}
while (((error = tcsetattr(fd, TCSAFLUSH, &term)) ==-1 && (errno == EINTR)))
return error;
}
int first_line(int *ptrc){
if (*ptrc != '\n' && *ptrc != '\r'){
putchar(*ptrc);
while (*ptrc != '\n'){
*ptrc = getchar();
putchar(*ptrc);
}
}
else return 0;
return 0;
}
Thanks Lachlan
P.S on a side point in my research I noticed someone saying Termios isn't "Standard C" is this because it is system dependant? (only for comments)

How would you expect this to work? If the input characters are sent to your program immediately, then by the time the backspace character is recieved it's simply too late for the terminal to handle backspace - your program has already seen the previous character, so it can't be taken back.
For example, say the user presses A. Your program will receieve 'A' from getchar() and process it. Now the user presses backspace - now what should the terminal do?
So this implies that the only place you can handle backspace in non-canonical mode is in your program itself. When you receive the '\b' character from getchar(), you can handle it specially (just like you have special handling for '\n') - for example, remove the most recently entered character from a buffer.

It's implementation-dependent. On my machine, pressing backspace led to the byte 127 being read by read(). This code worked on my machine.
#include <unistd.h>
#include <stdio.h>
#include <termios.h>
#include <string.h>
#include <stdlib.h>
#define MAXBUFSIZE (10000U)
#define DEL (127)
int main(void) {
char buf[MAXBUFSIZE];
char c;
size_t top;
struct termios curterm;
tcgetattr(STDIN_FILENO, &curterm);
curterm.c_lflag &= ~(ICANON| ECHO);
curterm.c_cc[VTIME] = 0;
curterm.c_cc[VMIN] = 1;
tcsetattr(STDIN_FILENO, TCSANOW, &curterm);
top = 0;
while (read(STDIN_FILENO, &c, sizeof c) == 1) {
switch (c) {
case DEL:
if (top) {
--top;
const char delbuf[] = "\b \b";
write(STDOUT_FILENO, delbuf, strlen(delbuf));
}
break;
case '\n':
write(STDOUT_FILENO, &c, sizeof c);
write(STDOUT_FILENO, buf, top);
top = 0;
break;
default:
buf[top++] = c;
write(STDOUT_FILENO, &c, sizeof c);
break;
}
}
return 0;
}

When the user presses backspace the application receives some control code. The application has to interpret the control code and take a character out of its buffer (application buffer since you are not using the kernel buffer). If the application is doing any kind of echoing (eg: echoing stars in place of the password characters) then it will have to send some other control codes to move the cursor left and blank out the last star.
Both the codes received for delete or backspace and the codes you send to move the cursor depend on the type of terminal the user has, so before you can do any of this jiggery-pokery you have to detect the terminal type as well. Most programmers don't want to spend time reading the manual for hundreds of different types of terminal, so they generally use a library that hides all of this from them. One such library is curses.

Related

How to remove characters(^\), ctrl + \(SIGQUIT), from STDIN in C [duplicate]

Good day,
I'm writing my own shell in C for my school which has to resemble bash as closely as possible.
I have to handle signals such as Ctrl-\ and Ctrl-C as bash does; for this reason I'm allowed to use signal function. It works fine, but the thing is whenever a Ctrl-C signal is caught (starting from the second catch), a ^C is printed.
On the net, I've found a workaround suggesting printing "\b \b\b \b\nminishell$ " whenever a Ctrl-C is caught, which will devour the two symbols. The thing is, since at the very first time ^C is not printed, the print devours two symbols of my prompting, making it just minishell instead of minishell$ , with the cursor incorrectly displayed.
Now I've come up with another workaround for this workaround which is to declare a static boolean to not print the baskspaces at the very first call. This doesn't help in case of Ctrl-\ though; Ctrl-\ proceeds to move my cursor to right when I attempt to write the two whitespaces that must replace the ^\.
I don't like these workarounds and would like to know whether there is a way to instruct the terminal not to output this stuff? I'm allowed to use tgetent, tgetflag, tgetnum, tgetstr, tgoto, tputs, tcsetattr, tcgetattr, have read their man pages but nothing seems to be helpful.
When you type a key on a terminal, two things happen
the character is echoed (displayed) on this terminal
the character is sent (over the line) to the attached program
Both these actions can be controlled via termios/tcsetattr(): a different character(s) can be sent or echoed, some can be suppressed, etc. (some/most of these actions take place in the terminal-driver , but this is not relevant here)
Demonstration: using tcsetattr() to control the echoing of the terminal:
#include <stdio.h>
#include <stdlib.h>
#define _SVID_SOURCE 1
#include <termios.h>
#include <unistd.h>
#include <signal.h>
struct termios termios_save;
void reset_the_terminal(void)
{
tcsetattr(0, 0, &termios_save );
}
sig_atomic_t the_flag = 0;
void handle_the_stuff(int num)
{
char buff[4];
buff[0] = '[';
buff[2] = '0' + num%10;
num /= 10;
buff[1] = '0' + num%10;
buff[3] = ']';
write(0, buff, sizeof buff);
the_flag = 1;
}
int main (void)
{
int rc;
int ch;
struct termios termios_new;
rc = tcgetattr(0, &termios_save );
if (rc) {perror("tcgetattr"); exit(1); }
rc = atexit(reset_the_terminal);
if (rc) {perror("atexit"); exit(1); }
termios_new = termios_save;
termios_new.c_lflag &= ~ECHOCTL;
rc = tcsetattr(0, 0, &termios_new );
if (rc) {perror("tcsetattr"); exit(1); }
signal(SIGINT, handle_the_stuff);
printf("(pseudoshell)Start typing:\n" );
while(1) {
ch = getc(stdin);
if (the_flag) {
printf("Saw the signal, last character was %02x\n", (unsigned) ch);
break;
}
}
exit (0);
}
The way to set the console such a SW may intercept all typed chars is to set the terminal in RAW MODE. The problems this way may present are that all keys that aren't in the ASCII 0-255 space, such as è, ì, à will be received from the console as a bytes sequence and all the function and control keys included cursors and backspace will not accomplish any action, some code such as CR, LF and some ANSI sequence may accomplish actions when are read from the input channel and rewritten on the output channel.
To set the terminal in raw mode you have to use the function cfmakeraw followed by the function tcsetattr.
The code below implements a simple but not very good implemented terminal, anyway I think this code is a good point to start. In any case, the code flow and the error control must be at least better arranged.
The code writes all sequence of ASCII char that enter into the console when a key is typed. All chars that have value smaller then 32 or greater then 126 will be written as [HEX-CODE]
I.E. hitting Esc on the console will be written [1B], the code of Ctrl+C will be written as [03], F1 will be [1B]OP, F11 will be [1B][23~, Enter will be [0D].
If you will hit Ctrl+X [18] will be written and the program stops, but this behaviour is under SW control as you can see in the code.
Here the code:
#include <stdio.h> // Standard input/output definitions
#include <string.h> // String function definitions
#include <unistd.h> // UNIX standard function definitions
#include <fcntl.h> // File control definitions
#include <errno.h> // Error number definitions
#include <termios.h> // POSIX terminal control definitions (struct termios)
#include <sys/ioctl.h> // Used for TCGETS2, which is required for custom baud rates
#include <sys/select.h> // might be used to manage select
int setAttr(int ch, int resetToOld);
#define IN 0
#define OUT 1
typedef struct TermCap
{
int fd;
struct termios oldTermios;
struct termios newTermios;
// fd_set fds; // might be used to manage select
} TermCap;
TermCap m_termCap[2];
int main()
{
int i,ex=0;
char msg;
char buff[20];
m_termCap[IN].fd=STDIN_FILENO;
m_termCap[OUT].fd=STDOUT_FILENO;
// Gets STDIN config and set raw config
setAttr(IN,0);
// Gets STDOUT config and set raw config
setAttr(OUT,0);
// Console loop ... the console terminates when ^X is intercepted.
do {
do {
i=read(m_termCap[IN].fd,&msg,1);
if (i>0){
if (msg<32 || msg>126) {
sprintf(buff,"[%02X]",(unsigned char)msg);
write(m_termCap[OUT].fd,buff,4);
if (msg==24)
ex=1;
}else{
write(m_termCap[OUT].fd,&msg,i);
}
}
usleep(10000); // a minimal delay of 10 millisec
} while(i>0 && !ex);
} while(!ex);
// Reset console to initial state.
setAttr(IN,1);
setAttr(OUT,1);
printf("\r\n\nThe end!");
return 0;
}
int setAttr(int ch, int resetToOld)
{
int retVal=0;
int i;
if (!resetToOld) {
// Read old term config
i=tcgetattr(m_termCap[ch].fd, &m_termCap[ch].oldTermios);
if (i==-1) {
return 1;
}
}
m_termCap[ch].newTermios = m_termCap[ch].oldTermios;
if (!resetToOld) {
// Terminal in raw mode
cfmakeraw(&m_termCap[ch].newTermios);
}
i=tcsetattr(m_termCap[ch].fd, TCSANOW, &m_termCap[ch].newTermios);
if (i==-1) {
retVal = 2;
}
return retVal;
}
Wouldn't this work?
void signalHandler(int signo){
if(signo==SIGINT){
printf("\b\b \b\b");
fflush(NULL);
printf("\nHello World\n");
}
}
In my shell it seems to work fine. The first printf and fflush is what you have to implement in your handler. The printf after that is just a way for me to show you that you can, then, do whatever you want after the ^C not appearing.
Why does this make it not appear? In the first printf I erase the characters by using backspaces and spaces. As stdout is buffered by default and I didn't want to use a newline character, I flushed the buffer manually.

Forcing a terminal not to print Ctrl hotkeys when signals are caught

Good day,
I'm writing my own shell in C for my school which has to resemble bash as closely as possible.
I have to handle signals such as Ctrl-\ and Ctrl-C as bash does; for this reason I'm allowed to use signal function. It works fine, but the thing is whenever a Ctrl-C signal is caught (starting from the second catch), a ^C is printed.
On the net, I've found a workaround suggesting printing "\b \b\b \b\nminishell$ " whenever a Ctrl-C is caught, which will devour the two symbols. The thing is, since at the very first time ^C is not printed, the print devours two symbols of my prompting, making it just minishell instead of minishell$ , with the cursor incorrectly displayed.
Now I've come up with another workaround for this workaround which is to declare a static boolean to not print the baskspaces at the very first call. This doesn't help in case of Ctrl-\ though; Ctrl-\ proceeds to move my cursor to right when I attempt to write the two whitespaces that must replace the ^\.
I don't like these workarounds and would like to know whether there is a way to instruct the terminal not to output this stuff? I'm allowed to use tgetent, tgetflag, tgetnum, tgetstr, tgoto, tputs, tcsetattr, tcgetattr, have read their man pages but nothing seems to be helpful.
When you type a key on a terminal, two things happen
the character is echoed (displayed) on this terminal
the character is sent (over the line) to the attached program
Both these actions can be controlled via termios/tcsetattr(): a different character(s) can be sent or echoed, some can be suppressed, etc. (some/most of these actions take place in the terminal-driver , but this is not relevant here)
Demonstration: using tcsetattr() to control the echoing of the terminal:
#include <stdio.h>
#include <stdlib.h>
#define _SVID_SOURCE 1
#include <termios.h>
#include <unistd.h>
#include <signal.h>
struct termios termios_save;
void reset_the_terminal(void)
{
tcsetattr(0, 0, &termios_save );
}
sig_atomic_t the_flag = 0;
void handle_the_stuff(int num)
{
char buff[4];
buff[0] = '[';
buff[2] = '0' + num%10;
num /= 10;
buff[1] = '0' + num%10;
buff[3] = ']';
write(0, buff, sizeof buff);
the_flag = 1;
}
int main (void)
{
int rc;
int ch;
struct termios termios_new;
rc = tcgetattr(0, &termios_save );
if (rc) {perror("tcgetattr"); exit(1); }
rc = atexit(reset_the_terminal);
if (rc) {perror("atexit"); exit(1); }
termios_new = termios_save;
termios_new.c_lflag &= ~ECHOCTL;
rc = tcsetattr(0, 0, &termios_new );
if (rc) {perror("tcsetattr"); exit(1); }
signal(SIGINT, handle_the_stuff);
printf("(pseudoshell)Start typing:\n" );
while(1) {
ch = getc(stdin);
if (the_flag) {
printf("Saw the signal, last character was %02x\n", (unsigned) ch);
break;
}
}
exit (0);
}
The way to set the console such a SW may intercept all typed chars is to set the terminal in RAW MODE. The problems this way may present are that all keys that aren't in the ASCII 0-255 space, such as è, ì, à will be received from the console as a bytes sequence and all the function and control keys included cursors and backspace will not accomplish any action, some code such as CR, LF and some ANSI sequence may accomplish actions when are read from the input channel and rewritten on the output channel.
To set the terminal in raw mode you have to use the function cfmakeraw followed by the function tcsetattr.
The code below implements a simple but not very good implemented terminal, anyway I think this code is a good point to start. In any case, the code flow and the error control must be at least better arranged.
The code writes all sequence of ASCII char that enter into the console when a key is typed. All chars that have value smaller then 32 or greater then 126 will be written as [HEX-CODE]
I.E. hitting Esc on the console will be written [1B], the code of Ctrl+C will be written as [03], F1 will be [1B]OP, F11 will be [1B][23~, Enter will be [0D].
If you will hit Ctrl+X [18] will be written and the program stops, but this behaviour is under SW control as you can see in the code.
Here the code:
#include <stdio.h> // Standard input/output definitions
#include <string.h> // String function definitions
#include <unistd.h> // UNIX standard function definitions
#include <fcntl.h> // File control definitions
#include <errno.h> // Error number definitions
#include <termios.h> // POSIX terminal control definitions (struct termios)
#include <sys/ioctl.h> // Used for TCGETS2, which is required for custom baud rates
#include <sys/select.h> // might be used to manage select
int setAttr(int ch, int resetToOld);
#define IN 0
#define OUT 1
typedef struct TermCap
{
int fd;
struct termios oldTermios;
struct termios newTermios;
// fd_set fds; // might be used to manage select
} TermCap;
TermCap m_termCap[2];
int main()
{
int i,ex=0;
char msg;
char buff[20];
m_termCap[IN].fd=STDIN_FILENO;
m_termCap[OUT].fd=STDOUT_FILENO;
// Gets STDIN config and set raw config
setAttr(IN,0);
// Gets STDOUT config and set raw config
setAttr(OUT,0);
// Console loop ... the console terminates when ^X is intercepted.
do {
do {
i=read(m_termCap[IN].fd,&msg,1);
if (i>0){
if (msg<32 || msg>126) {
sprintf(buff,"[%02X]",(unsigned char)msg);
write(m_termCap[OUT].fd,buff,4);
if (msg==24)
ex=1;
}else{
write(m_termCap[OUT].fd,&msg,i);
}
}
usleep(10000); // a minimal delay of 10 millisec
} while(i>0 && !ex);
} while(!ex);
// Reset console to initial state.
setAttr(IN,1);
setAttr(OUT,1);
printf("\r\n\nThe end!");
return 0;
}
int setAttr(int ch, int resetToOld)
{
int retVal=0;
int i;
if (!resetToOld) {
// Read old term config
i=tcgetattr(m_termCap[ch].fd, &m_termCap[ch].oldTermios);
if (i==-1) {
return 1;
}
}
m_termCap[ch].newTermios = m_termCap[ch].oldTermios;
if (!resetToOld) {
// Terminal in raw mode
cfmakeraw(&m_termCap[ch].newTermios);
}
i=tcsetattr(m_termCap[ch].fd, TCSANOW, &m_termCap[ch].newTermios);
if (i==-1) {
retVal = 2;
}
return retVal;
}
Wouldn't this work?
void signalHandler(int signo){
if(signo==SIGINT){
printf("\b\b \b\b");
fflush(NULL);
printf("\nHello World\n");
}
}
In my shell it seems to work fine. The first printf and fflush is what you have to implement in your handler. The printf after that is just a way for me to show you that you can, then, do whatever you want after the ^C not appearing.
Why does this make it not appear? In the first printf I erase the characters by using backspaces and spaces. As stdout is buffered by default and I didn't want to use a newline character, I flushed the buffer manually.

Non canonical terminal mode buffer stdout in c program

I am working a school project (building a very basic shell).
The idea is to be able to do line edition like in bash. For this, I change the terminal mode to non canonical and I stop echo.
I made a very simple code to expose my issue (please note, I do check for functions returns etc... I just made it as short as possible for this post)
#include <termios.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
int main()
{
int ret;
char buff;
char *term_name;
char *termcap;
struct termios termios_new;
struct termios termios_backup;
/*
** Init termcap library
*/
term_name = getenv("TERM");
tgetent(NULL, term_name);
/*
** Get the terminal mode to non canonical and shut down echo
*/
bzero(&termios_new, sizeof(struct termios));
tcgetattr(STDIN_FILENO, &termios_backup);
termios_new = termios_backup;
termios_new.c_lflag &= ~(ICANON);
termios_new.c_lflag &= ~(ECHO);
termios_new.c_cc[VMIN] = 1;
termios_new.c_cc[VTIME] = 0;
/*
** Set the change
*/
tcsetattr(STDIN_FILENO, TCSAFLUSH, &termios_new);
/*
** Get the termcap for clearing screen on the particular terminal
*/
termcap = tgetstr("cl", NULL);
/*
** Loop read to get user entries and clear screen for 'c', output char for 'b', break for 'q'
*/
while((ret = read(STDIN_FILENO, &buff, 1)) > 0)
{
if (buff == 'c')
tputs(termcap, 1, putchar);
else if (buff == 'b')
putchar(buff);
else if (buff == 'q')
break ;
buff = 0;
}
/*
** Put back the terminal mode as found before
*/
tcsetattr(STDIN_FILENO, TCSAFLUSH, &termios_backup);
return (0);
}
So basically it's a loop on read to catch user entries. It clears the screen for 'c', output char for 'b', break and restore the original terminal mode for 'q'.
Issue is :
Whenever I type anything, it seems to be buffered because nothing happens until I break the loop with 'q'.
At this moment, the output show on screen, if I typed 5 times b, I'll get the 5 b's and if I types 'c', the screen will be cleared.
BUT, only after typing 'q'.
The behaviour is the same while restoring or not the original terminal mode. (the last line before return)
What I suspect :
After making the code very short and checking all returns, I tend to think there could only be an issue with the way I change the terminal mode ?
I tries with the flags TCSAFLUSH and TCSADRAIN for the tcsetattr function with same result.
Thanks ! :)
✅ SOLVED :
Ok so for anyone who encounter this situation, it's quite interesting because it made me learn many stuffs (well it's a school project so...).
Using putchar in the tputs parameters was the issue, because putchar is buffered, juste like printf.
I ended up trying two things that led me to the realisation: fflush() after the tputs function call would work so obviously the issue was buffer related.
Then tried to output to STDERR_FILENO and it kinda solved it too, and in fact the stderr is the only one _IONBF, so not buffered.
So I ended up creating a putchar function with only write in it, and that was it.
Here are more info about the three buffer modes:
http://software-dl.ti.com/ccs/esd/documents/sdto_cgt_tips_for_using_printf.html

Backspace can't be detected in linux terminal , K&R 1.10 exercize

i was trying to do the execize 1.10 of k&r so this is :
/*
* Exercise 1.10
*
* Write a program to copy its input to its output, replacing each tab
* by \t, each backspace by \b, and each backslash by \\. This makes tab
* and backspaces visible in an unambiguous way.
*
*/
#include <stdio.h>
int main()
{
int c;
while ((c = getchar()) != EOF) {
if (c == '\t')
printf("\\t");
else if (c == '\b')
printf("\\b");
else if (c == '\\')
printf("\\\\");
else
printf("%c", c);
}
}
If i compile this code with gcc -std=c99 1.10.c -o test
it doesn't print \b if i use Backspace. Why? And how I could try to get \b pressing Backspace in Linux ?
A guy has said me:
Your program probably doesn't see that backspace. Terminals buffer by line, by default. So does stdin. Well, stdin's buffering is IDB.
Usually, the console interprets special characters, like backspace or Ctrl-d. You can instruct the console to not do this with stty.
Before you run the program, you can set the tty to ignore backspace mode with
stty erase ''
and restore the console afterwards with
stty sane
This passes the backspace characters Ctrl-h unchanged to the program you're running.
If this doesn't show any difference, then the backspace key may be mapped to DEL instead of Ctrl-h.
In this case you can just start your program and type Ctrl-h everywhere you would have used the backspace key. If you want to catch the backspace key nevertheless, you must also check for DEL, which is ASCII 127
/* ... */
else if (c == '\b' || c == 127)
printf("\\b");
#include <termios.h>
#include <unistd.h>
int mygetch(void)
{
struct termios oldt,
newt;
int ch;
tcgetattr( STDIN_FILENO, &oldt );
newt = oldt;
newt.c_lflag &= ~( ICANON | ECHO );
tcsetattr( STDIN_FILENO, TCSANOW, &newt );
ch = getchar();
tcsetattr( STDIN_FILENO, TCSANOW, &oldt );
return ch;
}
use getch then get the ASCII for backsapce using it, no need for enter or to clean input buffer and it returns an int.
Enjoy
Your code doesn't work because it's intended to run on very old UNIX machines. On modern terminals, the backspace character won't be considered.

Printing while reading characters in C

I'm trying to write a simple little snippet of code to respond to an arrow key press.
I know that up is represented by ^[[A, and I have the following code that checks for that sequence:
while( 1 )
{
input_char = fgetc( stdin );
if( input_char == EOF || input_char == '\n' )
{
break;
}
/* Escape sequence */
if( input_char == 27 )
{
input_char = getc( stdin );
if( input_char == '[' )
{
switch( getc( stdin ) )
{
case 'A':
printf("Move up\n");
break;
}
}
}
}
Whenever I hit "up", the escape sequence (^[[A) shows up on the screen, but "Move up" doesn't appear until I hit enter.
The end goal is to replace the text on the current line with some other data, and so I tried to do
printf("\r%s", "New Text");
in place of "Move up", but it still doesn't show up until after enter is pressed.
Is there something wrong with the way I'm reading in characters?
Thanks!
EDIT Quick note, it's for *nix systems.
SOLUTION
Thanks for the pointers everyone. I went with stepanbujnak's solution because it was rather straightforward. The one thing I noticed is that a lot of the behavior of keys that modify the string ( backspace, etc ) is different than you would expect. It will backspace through ANYTHING on the line (including printf'd stuff), and I had to account for that. After that it wasn't too bad getting the rest to fall in line :)
stdin is line buffered and hence getc(stdin) or fgetc(stdin) don't get to see those characters till you press ENTER See this link for more details
EDIT: If you don't want to get into ncurses there are other useful methods like setting the terminal to raw mode etc to overcome this limitation. Check this nice SO post
Capture characters from standard input without waiting for enter to be pressed
You actually only need to disable line buffering using termios
Here's an example of doing it:
#include <stdio.h>
#include <stdlib.h>
#include <termios.h>
int main() {
struct termios old_term, new_term;
char c;
/* Get old terminal settings for further restoration */
tcgetattr(0, &old_term);
/* Copy the settings to the new value */
new_term = old_term;
/* Disable echo of the character and line buffering */
new_term.c_lflag &= (~ICANON & ~ECHO);
/* Set new settings to the terminal */
tcsetattr(0, TCSANOW, &new_term);
while ((c = getchar()) != 'q') {
printf("You pressed: %c\n", c);
}
/* Restore old settings */
tcsetattr(0, TCSANOW, &old_term);
return 0;
}
Look at the curses library for capturing escape sequences such as arrow keys.
http://tldp.org/HOWTO/NCURSES-Programming-HOWTO/keys.html
On most systems keys such as arrow keys, home, page up, break etc are escaped keys they use an escape sequence to discern themselves. Something like 0x1B + Sequence, if you wanted to capture it raw you'd need to read the input directly from the file descriptor and listen for the sequences. The alternative is above using ncurses.
Outside of using curses the following illustrates how to accomplish this using system call(s) such as read
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
int fd = 0x0; /* STDIN */
size_t bytes_read;
char buf[12];
bytes_read = read(fd, buf, 3);
printf("%02x %02x %02x\n", buf[0], buf[1], buf[2]);
return 0;
}
output after pressing UP
Lukes-ASA-Macbook:tmp luke$ gcc -o out test.c
Lukes-ASA-Macbook:tmp luke$ ./out
^[[A
1b 5b 41
This should get you on your way.
You can buffer the input looking for 0x1b and then enable a parsing flag to look for an escape sequence of characters in lieu of single character parsing.

Resources