How to read terminal's input buffer immediately after keypress
Asked Answered
M

2

5

I want to read arrow keypresses in my c program and replace them(immediately in the terminal itself) by some other string. I am trying to implement the bash history functionality as in unix terminals. I wrote this code.

int
main(int argc, char *argv[])
{
    char c;
    char str[1024];
    int i = 0;
    while((c = fgetc(stdin)) != '\n'){
      if(((int)c) == 27){
        c=fgetc(stdin);
        c=fgetc(stdin);
        if (c == 'A')
        {
          printf("%c[A%c[2K",27, 27);
          printf("UP");
        }
      }
      str[i++] = c;
    }
    printf("\n");

  return 0;
}

But, this doesn't work because terminals wait for a newline or EOF to send the input buffer to stdin. So, I have to press enter/return key to analyze the user input.

Here in this answer the user mentioned to use system ("/bin/stty raw"); but this replaces all the default terminal behaviours (e.g. backspace, delete etc).

So, is there any way I can read/manipulate the terminals input buffer directly and tweak the buffer itself if arrow keypress is detected?

Environment - Ubuntu (Linux)

Update 1: Is there a method to change the signal/interrupt (default is pressing enter key) that makes the terminal send the stored input to buffer? This may also help me achieve the same behaviour.

Final Code:

I found out the ASCII characters for specific keypresses by checking output of strace bash

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <termios.h>

#define ESCAPE '\33'
#define BACKSPACE '\177'
#define DELETE '~'

int main(){
    struct termios termios_p;
    struct termios termios_save;
    tcgetattr(0, &termios_p);
    termios_save = termios_p;

    termios_p.c_lflag &= ~(ICANON|ECHO);
    tcsetattr(0,TCSANOW, &termios_p);
    char buff;
    while(read(0, &buff, 1) >= 0){
        if(buff > 0){
            if(buff == ESCAPE){
                read(0, &buff, 1);
                read(0, &buff, 1);
                if(buff == 'A'){
                    write(2, "up", 2);
                }else if(buff == 'B'){
                    write(2, "down", 4);

                }else if(buff == 'C'){
                    write(2, "\33[C", 3);

                }else if(buff == 'D'){
                    write(2, "\10", 2);                    
                }
            }else if(buff == BACKSPACE){
                write(2, "\10\33[1P", 5);
            }else if(buff == DELETE){
                write(2, "\33[1P", 4);
            }else{
                write(2,&buff,1);
            }
            // write(2,&buff,1);
            if(buff == 'q'){
                break;
            }
        }
    }
    tcsetattr(0,TCSANOW, &termios_save);
    return 0;
}
Monogenic answered 12/8, 2015 at 11:24 Comment(4)
You should add "linux" as a tag, since this is system-dependent, and from your "/bin/stty raw" (and statistics) I assume you are on Linux?Cleavers
@ThomasPadron-McCarthy yes, I am in ubuntuMonogenic
Look here: #7469639Saskatchewan
Why not just use the readline library?Pet
D
9

It seems you are looking for something like this.

The program essentially waits for user input. If up arrow key is pressed, the program prints "Arrow key pressed" and then exits. If anything else is pressed, it waits for the user to complete what he is typing and prints it, then exits.

#include <termios.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
int main()
{
  struct termios oldt, newt;
  char ch, command[20];
  int oldf;

  tcgetattr(STDIN_FILENO, &oldt);
  newt = oldt;
  newt.c_lflag &= ~(ICANON | ECHO);
  tcsetattr(STDIN_FILENO, TCSANOW, &newt);
  oldf = fcntl(STDIN_FILENO, F_GETFL, 0);
  fcntl(STDIN_FILENO, F_SETFL, oldf | O_NONBLOCK);

  while(1)
  {
    ch = getchar();
    if (ch == '\033')
    { printf("Arrow key\n"); ch=-1; break;}
    else if(ch == -1) // by default the function returns -1, as it is non blocking
    {
      continue;
    }
    else
    {
      break;
    }

  }
  tcsetattr(STDIN_FILENO, TCSANOW, &oldt);
  fcntl(STDIN_FILENO, F_SETFL, oldf);

   if(ch != EOF)
   {
      ungetc(ch,stdin);ith
      putchar(ch);
      scanf("%s",command);
      printf("\n%s\n",command);

      return 1;
    }

    return 0;
  }
Dombrowski answered 12/8, 2015 at 15:41 Comment(3)
@bishal - You might need to fine tune the flags after calling tcgetattr to get it as per your requirments. Here you can see the getchar has become nonblocking. thats why I needed to put the condition for -1Dombrowski
Thanks this is the code I was looking for. I had to do some more tweaking to get a similar behaviour like terminal. I have added my the code above.Monogenic
😂! a variable named newtNo
B
3

On my Linux Machine I use ncurses:

#include<stdio.h>
#include<ncurses.h>

void checkArrow(){
    int ch;

    initscr();
    raw();
    keypad(stdscr, TRUE);
    noecho();

    printw("Press q to Exit\t");

    do{
        switch(ch){
            case KEY_UP: {
                printw("\n\nUp Arrow\n");
                printw("Press q to Exit\t");
                break;
            }
            case KEY_DOWN:{
                printw("\n\nDown Arrow\n");
                printw("Press q to Exit\t");
                break;
            }
            case KEY_LEFT:{
                printw("\n\nLeft Arrow\n");
                printw("Press q to Exit\t");
                break;
            }
            case KEY_RIGHT:{
                printw("\n\nRight Arrow\n");
                printw("Press q to Exit\t");
                break;
            }
        }
    }while((ch = getch()) != 'q');

    printw("\n\n\t\t\t-=GoodBye=-!\n");
    refresh();
    endwin();
}

int main(){
    checkArrow();

    return 0;
}
Up Arrow
Press q to Exit 
Down Arrow
Press q to Exit 

Left Arrow
Press q to Exit 

Right Arrow
Press q to Exit 
Billye answered 12/8, 2015 at 11:38 Comment(1)
Your code works... But read my question, I said "Keep the default terminal behaviours", I need it to work exactly like normal terminal. Your code behaves very similar to using system ("/bin/stty raw");Monogenic

© 2022 - 2024 — McMap. All rights reserved.