Using kbhit() and getch() on Linux
Asked Answered
S

6

18

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?

Stroman answered 29/3, 2015 at 22:36 Comment(3)
The ncurses library should help.Lackadaisical
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.Langur
ncurses worked perfectly, thanks!Stroman
S
14

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.

Spiraea answered 29/3, 2015 at 23:7 Comment(5)
afaik you should add an endwin() at the end of the programBrutal
only needed if the program exits -- it does not, in OP's question.Spiraea
It does not. It loops forever.Spiraea
ohh... sorry ;) but dont you have to reset the terminal?Brutal
not in this case: one could exit with a ^C, and ncurses would reset as described in endwin.Spiraea
D
19

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.

Doomsday answered 29/3, 2015 at 22:40 Comment(4)
The getch is probably more important to @Boxiom.Langur
This answer solved my issue, so thanks Christophe. But I would like to add that "#include <sys/ioctl.h>" 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.Cream
stropts.h no such file or directory. apparently linux doesn't support this anymoreChaoan
@Chaoan indeed, there seem to be some recent changes on the linux world: 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.Doomsday
S
14

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.

Spiraea answered 29/3, 2015 at 23:7 Comment(5)
afaik you should add an endwin() at the end of the programBrutal
only needed if the program exits -- it does not, in OP's question.Spiraea
It does not. It loops forever.Spiraea
ohh... sorry ;) but dont you have to reset the terminal?Brutal
not in this case: one could exit with a ^C, and ncurses would reset as described in endwin.Spiraea
J
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)

Juvenilia answered 12/7, 2017 at 21:11 Comment(1)
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://mcmap.net/q/116837/-capture-characters-from-standard-input-without-waiting-for-enter-to-be-pressedScorify
C
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.

Carriecarrier answered 29/3, 2015 at 23:13 Comment(1)
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.Spiraea
N
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;
}
Ninth answered 2/9, 2020 at 15:30 Comment(0)
N
0

On Linux, if you use ANSI escapes to address each target line, the text is printed immediately with no obvious need to flush. However, eventually the buffer becomes full and the printf function stalls and does not return. The same code run on Windows 10 does not suffer from this problem. (I was unable to find this discussion until after I had diagnosed the need for flush. Time spent was measured in days).

Nit answered 11/7 at 18:27 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.