17

On Windows, I have the following code to look for input without interrupting the loop:

#include <conio.h>
#include <Windows.h>
#include <iostream>

int main()
{
    while (true)
    {
        if (_kbhit())
        {
            if (_getch() == 'g')
            {
                std::cout << "You pressed G" << std::endl;
            }
        }
        Sleep(500);
        std::cout << "Running" << std::endl;
    }
}

However, seeing that there is no conio.h, whats the simplest way of achieving this very same thing on Linux?

Boxiom
  • 2,245
  • 6
  • 40
  • 51
  • 1
    The [*ncurses*](https://en.wikipedia.org/wiki/Ncurses) library should help. – 5gon12eder Mar 29 '15 at 22:43
  • 1
    I've never actually done this, becasue doing this on linux is harder than what you had for windows. I will leave [a link to an NCURSES howto on doing this with ncurses.h though.](http://tldp.org/HOWTO/NCURSES-Programming-HOWTO/scanw.html#GETCHCLASS) – Matthew Carlson Mar 29 '15 at 22:44
  • ncurses worked perfectly, thanks! – Boxiom Mar 30 '15 at 02:11

5 Answers5

18

If your linux has no conio.h that supports kbhit() you can look here for Morgan Mattews's code to provide kbhit() functionality in a way compatible with any POSIX compliant system.

As the trick desactivate buffering at termios level, it should also solve the getchar() issue as demonstrated here.

HoldOffHunger
  • 18,769
  • 10
  • 104
  • 133
Christophe
  • 68,716
  • 7
  • 72
  • 138
  • The getch is probably more important to @Boxiom. – Matthew Carlson Mar 29 '15 at 22:42
  • 4
    This answer solved my issue, so thanks Christophe. But I would like to add that "#include " was required for me to get Morgan McGuire's function to work in my program due to the use of FIONREAD, and that isn't explicitly listed as necessary on the web page where his code is found. – user251563 Nov 22 '15 at 23:59
  • 3
    stropts.h no such file or directory. apparently linux doesn't support this anymore – elig Jul 29 '20 at 17:21
  • 1
    @elig indeed, there seem to be some recent changes on the linux world: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=954552 - There are already some recent answers about similar issues with stropts.h on SO: maybe you find one suitable, maybe you open a new one. It would be great to add a comment if you find a solution for those who might experience the same issue with the same distro. – Christophe Jul 29 '20 at 17:46
13

The ncurses howto cited above can be helpful. Here is an example illustrating how ncurses could be used like the conio example:

#include <ncurses.h>

int
main()
{
    initscr();
    cbreak();
    noecho();
    scrollok(stdscr, TRUE);
    nodelay(stdscr, TRUE);
    while (true) {
        if (getch() == 'g') {
            printw("You pressed G\n");
        }
        napms(500);
        printw("Running\n");
    }
}

Note that with ncurses, the iostream header is not used. That is because mixing stdio with ncurses can have unexpected results.

ncurses, by the way, defines TRUE and FALSE. A correctly configured ncurses will use the same data-type for ncurses' bool as the C++ compiler used for configuring ncurses.

Thomas Dickey
  • 51,086
  • 7
  • 70
  • 105
9

A compact solution based on Christophe's answer is

#include <sys/ioctl.h>
#include <termios.h>

bool kbhit()
{
    termios term;
    tcgetattr(0, &term);

    termios term2 = term;
    term2.c_lflag &= ~ICANON;
    tcsetattr(0, TCSANOW, &term2);

    int byteswaiting;
    ioctl(0, FIONREAD, &byteswaiting);

    tcsetattr(0, TCSANOW, &term);

    return byteswaiting > 0;
}

Unlike that answer, this won't leave the terminal in a weird state after the program has exited. However, it still leaves the characters sitting in the input buffer, so the key that was pressed will unwelcomely appear on the next prompt line.

A different solution which fixes this problem is

void enable_raw_mode()
{
    termios term;
    tcgetattr(0, &term);
    term.c_lflag &= ~(ICANON | ECHO); // Disable echo as well
    tcsetattr(0, TCSANOW, &term);
}

void disable_raw_mode()
{
    termios term;
    tcgetattr(0, &term);
    term.c_lflag |= ICANON | ECHO;
    tcsetattr(0, TCSANOW, &term);
}

bool kbhit()
{
    int byteswaiting;
    ioctl(0, FIONREAD, &byteswaiting);
    return byteswaiting > 0;
}

Usage is as follows

enable_raw_mode();
// ...
if (kbhit()) ...
// ...
disable_raw_mode();
tcflush(0, TCIFLUSH); // Clear stdin to prevent characters appearing on prompt

Now any characters typed between execution of the first and last lines won't show up in the terminal. However, if you exit with Ctrl+C the terminal is left in a weird state. (Sigh)

PBS
  • 1,389
  • 11
  • 20
  • This works for determining if a key was pressed, but provides no way of getting the corresponding character (if you use getc(), you're still left waiting for the user to press Enter). See this solution if you want to wait for a key to be pressed and immediately get the character: https://stackoverflow.com/a/912796/544099 – ishmael Feb 25 '19 at 13:47
3

While using ncurses is functionally equivalent to the Turbo C "conio.h" API, a more complete solution is to use a conio implementation, as can be found here.

You download and use it in your program for a very complete implementation of the conio interface, on Linux. (Or OSX.) Written by Ron Burkey.

Prof. Falken
  • 24,226
  • 19
  • 100
  • 173
  • A note on the Turbo C page states that mixing stream-based functionality with conio probably won't work. That eliminates the principal reason one might want to use it, since the OP's example is not guaranteed to work. – Thomas Dickey Mar 30 '15 at 00:30
3

If you are using Linux, I found this solution where you can create your own local library:

http://linux-sxs.org/programming/kbhit.html

kbhit.cpp


#include "kbhit.h"
#include <unistd.h> // read()
    
keyboard::keyboard(){
    tcgetattr(0,&initial_settings);
    new_settings = initial_settings;
    new_settings.c_lflag &= ~ICANON;
    new_settings.c_lflag &= ~ECHO;
    new_settings.c_lflag &= ~ISIG;
    new_settings.c_cc[VMIN] = 1;
    new_settings.c_cc[VTIME] = 0;
    tcsetattr(0, TCSANOW, &new_settings);
    peek_character=-1;
}
    
keyboard::~keyboard(){
    tcsetattr(0, TCSANOW, &initial_settings);
}
    
int keyboard::kbhit(){
    unsigned char ch;
    int nread;
    if (peek_character != -1) return 1;
    new_settings.c_cc[VMIN]=0;
    tcsetattr(0, TCSANOW, &new_settings);
    nread = read(0,&ch,1);
    new_settings.c_cc[VMIN]=1;
    tcsetattr(0, TCSANOW, &new_settings);

    if (nread == 1){
        peek_character = ch;
        return 1;
    }
    return 0;
}
    
int keyboard::getch(){
    char ch;

    if (peek_character != -1){
        ch = peek_character;
        peek_character = -1;
    }
    else read(0,&ch,1);
    return ch;
}

kbhit.h

#ifndef KBHIT_H
#define KBHIT_H
    
#include <termios.h>
    
class keyboard{
    public:
        keyboard();
        ~keyboard();
        int kbhit();
        int getch();

    private:
        struct termios initial_settings, new_settings;
        int peek_character;
};
    
#endif

inside main.cpp I created an instance:

#include "kbhit.h"

int main(){
    int key_nr;
    char key;
    keyboard keyb;
    while(true){
        if( keyb.kbhit() ){
            key_nr = keyb.getch(); //return int
            key = key_nr; // get ascii char
            // do some stuff
        }
    }
    return 0;
}