3

I have a getch() function which my tutor gave me, which is getting input from the keyboard without clicking on 'ENTER'. But, when I run it in Ubuntu 12 in Eclipse, I get the following error:

tcsetattr(): Inappropriate ioctl for device
tcsetattr ICANON: Inappropriate ioctl for device

This is my code:

#include <stdio.h>
#include <unistd.h>
#include <termios.h>

char getch();

int main(int argc, const char* argv[])
{
    char c;
    do
    {
        c=getch();
        printf("%c",c);
    } while(c!='q');

    return 0;
}

char getch()
{
    char buf = 0;
    struct termios old = {0};
    if (tcgetattr(0, &old) < 0)
        perror("tcsetattr()");
    old.c_lflag &= ~ICANON;
    old.c_lflag &= ~ECHO;
    old.c_cc[VMIN] = 1;
    old.c_cc[VTIME] = 0;

    if (tcsetattr(0, TCSANOW, &old) < 0)
        perror("tcsetattr ICANON");
    if (read(0, &buf, 1) < 0)
        perror ("read()");
    old.c_lflag |= ICANON;
    old.c_lflag |= ECHO;
    if (tcsetattr(0, TCSADRAIN, &old) < 0)
        perror ("tcsetattr ~ICANON");
    return (buf);
}

NOTE: The code DOES work in SSH Secure Shell. But I have to get this work in my Ubuntu, since I write my code there. Thanks

austin
  • 5,816
  • 2
  • 32
  • 40
Jjang
  • 11,250
  • 11
  • 51
  • 87

1 Answers1

4

This is probably because Eclipse is not providing a pseudoterminal to programs run under the IDE. Try this alternative which relies on nonblocking I/O rather than terminal controls.

#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#define perror_exit(msg) do { perror(msg); exit(1); } while(0)

#if defined EAGAIN && defined EWOULDBLOCK
#define retry_p(err) ((err) == EAGAIN || (err) == EWOULDBLOCK)
#elif defined EAGAIN
#define retry_p(err) ((err) == EAGAIN)
#elif defined EWOULDBLOCK
#define retry_p(err) ((err) == EWOULDBLOCK)
#else
#error "Don't know how to detect read-would-block condition."
#endif

int
main(void)
{
    int flags = fcntl(0, F_GETFL);
    if (flags == -1)
        perror_exit("fcntl(F_GETFL)");
    flags |= O_NONBLOCK;
    if (fcntl(0, F_SETFL, flags))
        perror_exit("fcntl(F_SETFL, O_NONBLOCK)");

    for (;;)
    {
       char ch;
       ssize_t n = read(0, &ch, 1);
       if (n == 1)
       {
           putchar(ch);
           if (ch == 'q')
               break;
       }
       else if (n < 0 && !retry_p(errno))
          perror_exit("read");
    }
    return 0;
}

If this still doesn't work, try modifying it to do both what this does and what your getch() does, ignoring failure of tcsetattr and tcgetattr when errno == ENOTTY.

Note that both this and your original code are busy-waiting for I/O, which is bad practice. What you really should be doing is using the ncurses library, which has a more sophisticated approach to simultaneous processing and waiting for input that fits better with a multitasking environment, and also deals with your lack-of-a-tty problem and any number of other low-level headaches that you don't want to waste effort on.

zwol
  • 135,547
  • 38
  • 252
  • 361
  • Incidentally, if it doesn't have a pseudoterminal, the underlying read from 0 would block as it is a pipe. – Joshua Apr 19 '13 at 16:54
  • Thanks for the effort. I also tried running it from the Terminal. Does the Terminal lack the same property Eclipse lacks? Or it should work there? – Jjang Apr 19 '13 at 16:57
  • The original program should have worked in any context that calls itself a "terminal". I can imagine it failing due to subtleties of either the specific program that implements "the Terminal", or the specific OS you are using. This is exactly the sort of problem that `ncurses` will smooth over for you. – zwol Apr 19 '13 at 18:20