Become a MacRumors Supporter for $50/year with no ads, ability to filter front page stories, and private forums.

Kew

macrumors newbie
Original poster
Aug 6, 2015
1
0
i am using Mac os X, which running Unix system. i had tried getch() function which can't work in this case. I am wondering is that any method to make the function wait for an input without blocking? Thank you

Here is the code:

Code:
#include <iostream>
#include <curses.h>

// checks if a key a has been pressed(non-blocking)
int kbhit()
{
 
    int ch;
    //getchar();
    ch = getch();
    //ch = 'a';
    //system("stty cbreak -echo");
 
    if (ch != ERR) {
        //ungetch(ch);
        return 1;
    } else {
        return 0;
    }
}

using namespace std;


int main()
{

    bool gameover = false;
    nodelay(stdscr, TRUE);
    //system("stty raw");

    while(!gameover)
    {
     
        if(kbhit())
        {
            cout << "You enter something" << endl;
            cout << kbhit() << endl;
            gameover = true;
        }
        else
        {
            cout << "nothing is enter\n";
            cout << kbhit() << endl;
            gameover = false;
        }
    }

    //system ("stty cooked");
    //system("stty cooked echo");
 

    return 0;
}
 
Last edited by a moderator:
i had tried getch() function which can't work in this case.

getch is a curses function. Are you otherwise using curses? Have you called initscr? I think curses can probably do what I think it is you want but if you want to use curses you should do all your I/O with it, not just bits an pieces. Go find a curses tutorial and follow it and skip the rest of this.

I am wondering is that any method to make the function wait for an input without blocking?

Waiting for input without blocking doesn't make too much sense because blocking _is_ waiting. Perhaps you mean you wish to read input without blocking? If so there are a number of issues you need to consider - it's not that simple and it's not that portable.

First you should block on something. If not then you will just spin on the CPU, checking millions of times a second if anything has happened only to be told no. You will quickly hit 100% CPU, flattening batteries, running fans, and slowing down everything else. The question of what you block on will depend on what the other inputs to your program are (maybe your program reads from the network as well?) but you should checkout select(2) or kqueue(2) if you aren't already aware of them. That way you can wait for some input to be ready before trying to read. Input, especially from a user, is very slow compared to your CPU so if you use select or kqueue your program can spend most of its time sleeping.

Second is that a terminal will buffer input by default so it's not available to your application until the user has hit return. You can adjust that (and I see you have tried to with your call to stty) using tcgetattr(3) and tcsetattr(3).

Third you need to open your file descriptors in non blocking mode so if you do attempt to read when there is no data available to be read you get an error, rather than blocking. This unfortunately precludes the use of stdio functions and (though I'm not a c++ guy) I think also C++'s default iostream stuff.

I see you are using C++ but I never do so here is a small example in C that might help. It is hopefully fairly portable to most UNIX like systems though this stuff is inherently OS specific so I make no guarantees. I've also used blocks - probably not portable to older compilers but very nice to use and support under OSX by default so why not :)

Code:
#include <sys/select.h>

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


/*
   Example code to do non-blocking reads from stdin

   NB this uses blocks so on some platforms you might need "-fblocks" to
   compile
*/
int main(void) {

    /* make stdin non-blocking */
    {
        int f;

        // fetch the current flags
        if ((f = fcntl(STDIN_FILENO, F_GETFL, 0)) == -1) {
            err(1, "fcntl(GETFL)");
        }
        // now set the flags to what they are + non-blocking
        if ((f = fcntl(STDIN_FILENO, F_SETFL, f | O_NONBLOCK)) == -1) {
            err(1, "fcntl(SETFL)");
        }
    }

    /* try to unbuffer our terminal (if indeed that is what stdin is connected
      to) */
    {
        struct termios termios;

        // get our current terminal settings
        if (tcgetattr(STDIN_FILENO, &termios) != 0) {
            // NB if stdin isn't a terminal (e.g. we are reading from a file
            // or a pipe) then this will fail. good software wouldn't exit in
            // that case - luckily we are just example code.
            err(1, "tcgetattr");
        }
        // when we exit we need to restore the terminal the way it was rather
        // than leave it in a broken state. most shells will actually clean up
        // after us so this won't always be needed but best to be polite.
        atexit_b(^{
                tcsetattr(STDIN_FILENO, TCSASOFT, &termios);
                // we don't error check this becuase if it fails there isn't
                // anything we can do about it
            });
        // build a description of the mode we would like the terminal to be in
        // by starting with the current settings and changing a few things.
        // first switch of canonical mode and echo. switching off echo isn't
        // actually required but often wanted. switching off canonical mode
        // means we don't have to wait for a newline to be able to read
        termios.c_lflag &= ~(ICANON | ECHO);
        // then make sure read will return straight away
        termios.c_cc[VMIN] = 0;
        termios.c_cc[VTIME] = 0;
        // now apply those settings to the terminal
        if (tcsetattr(STDIN_FILENO, TCSASOFT, &termios) != 0) {
            err(1, "tcsetattr");
        }
    }

    /* now it should be safe for us to read from the terminal without
      blocking. to prevent us spinning we use select to wait till there is
      (probably) something to read */
    {
        fd_set fd_set;

        for (;;) {
            // select needs a set of descriptors we want to know about. that's
            // only 1 in this trivial example
            FD_ZERO(&fd_set);
            FD_SET(STDIN_FILENO, &fd_set);

            // now call select. NB the first argument is the maximum fd + 1 -
            // not the number of fds in the set. our timeout is NULL so
            // select should never return 0. we should block here until there
            // is data to be read
            if (select(STDIN_FILENO + 1, &fd_set, NULL, NULL, NULL) == -1) {
                if (errno == EINTR) {
                    // this isn't really an error so just try again
                    continue;
                } else {
                    err(1, "select");
                }
            }

            // check if the fd of interest is in the set of read fds (we only
            // have one so this is very unlikely to ever be false). the set contains
            // the gds that can be read from without blocking
            if (FD_ISSET(STDIN_FILENO, &fd_set)) {
                char buffer[32];
                int n;

                // we should have something to read from stdin so try it - it may
                // be more than 1 byte though unlikely if coming from a keyboard
                // we should, but don't, check for read returning all 32 bytes as
                // that could mean we should call read again without looping
                // through the select again (just for efficiency)
                switch (n = read(STDIN_FILENO, (void *)buffer, sizeof(buffer))) {
                    case -1:
                        if (errno == EAGAIN) {
                            // there wasn't anything to read after all
                            continue;
                        } else {
                            err(1, "read");
                        }
                        break;
                    case 0:
                        // EOF
                        exit(0);
                        break;
                    default:
                        // print out what we read
                        printf("Read: %*s\n", n, buffer);
                        break;
                }
            } else {
                // we only had one fd in the set yet "another" fd caused
                // select to return. I doubt this will ever happen (famous
                // last words)
                warnx("select returned with unknown fd");
            }
        }
    }

    return 0;
}
 
Last edited:
  • Like
Reactions: chown33
You can do this with curses, but as been hinted at you need to initialize a window and then clean up before you exit to restore the terminal. For a non-blocking read you can set timeout(0) at the initialization, that's it.

Reading input this way is problematic though because your process will consume 100% of your CPU, doing nothing most of the time.
 
I add a small example showing how you can do this with curses, letting you draw a "line" with the w,a,s,d keys in the terminal window, since it looks like your purpose is some kind of game. I just use the default window size, but you can set this up and also check your current terminal size first, look for a curses tutorial online.

Code:
#include <curses.h>
#include <stdlib.h>
#include <unistd.h>

struct cursor {
  char x;
  char y;
};

void read_wasd(struct cursor *c) {

  char ch = getch();

  switch(ch) {
  case 'a':
    c->x--;
    break;
  case 'd':
    c->x++;
    break;
  case 'w':
    c->y--;
    break;
  case 's':
    c->y++;
    break;
  }
}

int main(void)
{
  // init curses
  initscr();
  cbreak();
  noecho();
  timeout(0);  // sets input to non-blocking

  struct cursor cursor = { 0, 0 };

  atexit((void*)&endwin);
  mvprintw(cursor.y, cursor.x, "*");

  while(1) {
    read_wasd(&cursor);
    mvprintw(cursor.y, cursor.x, "*");
    usleep(10000);
  }

  return 0;
}

I added a usleep() at the end to reduce CPU use, you could add game logic in this loop as well, so it seems this would work since you need to update this periodically anyway, so you may as well check if your cursor position has changed at the same time.
 
Register on MacRumors! This sidebar will go away, and you'll see fewer ads.