Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to read terminal's input buffer immediately after keypress

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;
}
like image 777
bytestorm Avatar asked Dec 24 '22 15:12

bytestorm


2 Answers

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;
  }
like image 145
Amrith Krishna Avatar answered Jan 05 '23 15:01

Amrith Krishna


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 
like image 39
Michi Avatar answered Jan 05 '23 16:01

Michi