4

I have a chat client that takes in input in raw terminal mode, but I don't know about handling input in this mode. I need to know 2 things:

  • How can I read the input in character-by-character and display it? Do I have to have some sort of a read loop that reads one character at a time and stores it in a buffer?
  • If I want my server to process the input on new line entry, do I have to scan each character as it comes into my buffer and look for \n?

Also, an example character-by-character read loop that flushes on \n would be really great to see. Thanks!

theeggman85
  • 1,745
  • 6
  • 23
  • 36
  • 1
    There's not much point in using raw mode if you only flush the output on receiving `'\n'`, is there? – Jonathan Leffler Oct 29 '12 at 00:51
  • The client needs to be able to print out incoming messages from other clients in the chatroom while the user is typing, so it needs to print some backspaces to put the new chats above what the user is typing. – theeggman85 Oct 29 '12 at 02:20

2 Answers2

7

In Windows there is a very useful function kbhit() but that is not there in linux unfortunately. There can be multiple methods for this. we'll make our own kbhit() method for linux. kbhit() will return non zero value when something is detected in the input buffer otherwise it will return 0 and pass on. In simple words it is non-blocking. Whenever the result is true then call getch() method to fetch that pressed key.

#include <stdio.h>
#include <termios.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/time.h>

void changemode(int);
int  kbhit(void);
int main(void)
{
  int ch;
  changemode(1);
  while ( !kbhit() );      // Waiting for some keyboard input.

  // something has been detected. now get that.
  ch = getchar();

  printf("\nGot %c\n", ch);

  changemode(0);
  return 0;
}

void changemode(int dir)
{
  static struct termios oldt, newt;

  if ( dir == 1 )
  {
    tcgetattr( STDIN_FILENO, &oldt);
    newt = oldt;
    newt.c_lflag &= ~( ICANON | ECHO );
    tcsetattr( STDIN_FILENO, TCSANOW, &newt);
  }
  else
    tcsetattr( STDIN_FILENO, TCSANOW, &oldt);
}

int kbhit (void) 
{
  struct timeval tv;
  fd_set rdfs;

  tv.tv_sec = 0;
  tv.tv_usec = 0;

  FD_ZERO(&rdfs);
  FD_SET (STDIN_FILENO, &rdfs);

  select(STDIN_FILENO+1, &rdfs, NULL, NULL, &tv);
  return FD_ISSET(STDIN_FILENO, &rdfs);

}
Kanwar Saad
  • 2,138
  • 2
  • 21
  • 27
5

I recommend the GNU readline library for this. It takes care of the tedious work of getting lines of input, and allows the user to edit his line with backspace, left and right arrows, etc, and to recall older command using the up arrow and even search for older command using ^R, etc. Readline comes installed with typical unix-like distributions like linux, but if you don't have it, you can find it here

Edit: Here is a minimal readline example:

#include <stdio.h>
#include <readline/readline.h>
#include <readline/history.h>

int main(int argc, char ** argv)
{
    while(1)
    {
        char * line = readline("> ");
        if(!line) break;
        if(*line) add_history(line);
        /* Do something with the line here */
    }
}

Compile with gcc -o test test.c -lreadline -lncurses.

If you can't use readline, getline is an alternative:

#include <stdio.h>
int main()
{
    char * line = NULL;
    size_t len;
    while(getline(&line, &len, stdin) >= 0)
       printf("I got: %s", line);
}

If even getline is unacceptable, you can use fgets. It will not dynamically allocate a buffer of suitable size, so too long lines will be truncated. But at least it is standard C:

#include <stdio.h>
int main()
{
    char buf[1000];
    while(fgets(buf, sizeof(buf), stdin)
        printf("I got: %s, line);      
}
amaurea
  • 4,950
  • 26
  • 35
  • I'm actually working on an assignment where no libraries are allowed. This library seems really neat though, I'll check it out in my spare time. Is it much harder to do manually? – theeggman85 Oct 29 '12 at 00:25
  • If you can't use readline, getline is a less powerful alternative. It can be found in , but it is not standard C - it is a POSIX extension, so it won't be available everywhere. I will update the answer with an example of this. – amaurea Oct 29 '12 at 00:29
  • Thanks! getline will only get the line though right? So if I'm in raw mode, how do I show the characters as they are typed? – theeggman85 Oct 29 '12 at 00:37
  • Oh, I missed the raw part. I have only directly accessed the TTY using libraries like ncurses, so I don't think I can help you. A google search got me [this example](http://www.cs.uleth.ca/~holzmann/C/system/ttyraw.c), but it doesn't do the character echoing you want. Sorry about that. – amaurea Oct 29 '12 at 00:46
  • This isn't answering OPs question at ALL. All of these input methods are buffered and do not give the user one character at a time raw keyboard input. – Carlo Wood Apr 18 '21 at 08:00