Utilizarea kbhit() și getch() pe Linux (Programare, C++, Linux, Getch, Conio, Kbhit)

Boxiom a intrebat.

Pe Windows, am următorul cod pentru a căuta intrări fără a întrerupe bucla:

#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;
    }
}

Cu toate acestea, văzând că nu există conio.h, , care este cea mai simplă modalitate de a realiza același lucru pe Linux?

Comentarii

5 răspunsuri
Thomas Dickey

Howto-ul ncurses citat mai sus poate fi de ajutor. Iată un exemplu care ilustrează modul în care ncurses ar putea fi utilizat ca exemplul conio:

#include <ncurses.h>

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

Rețineți că, cu ncurses, se utilizează iostream nu este utilizat. Acest lucru se datorează faptului că amestecarea stdio cu ncurses poate avea rezultate neașteptate.

Apropo, ncurses definește TRUE și FALSE. Un ncurses configurat corect va utiliza același tip de date pentru ncurses’. bool pe care compilatorul C++ l-a folosit pentru configurarea ncurses.

Comentarii

  • De fapt, ar trebui să adăugați un endwin() la sfârșitul programului –  > Por largest_prime_is_463035818.
  • necesar doar dacă programul iese – nu o face, în întrebarea lui OP. –  > Por Thomas Dickey.
  • Nu este așa. Acesta se învârte în buclă la nesfârșit. –  > Por Thomas Dickey.
  • ohh… scuze 😉 dar nu trebuie să resetezi terminalul? –  > Por largest_prime_is_463035818.
  • nu și în acest caz: se poate ieși cu un ^C, , iar ncurses s-ar reseta așa cum este descris în endwin. –  > Por Thomas Dickey.
Christophe

Dacă linuxul tău nu are conio.h care suportă kbhit() vă puteți uita aici pentru codul lui Morgan Mattews pentru a furniza kbhit() funcționalitatea într-un mod compatibil cu orice sistem compatibil POSIX.

Deoarece trucul dezactivează buffering-ul la nivel de termios, ar trebui să rezolve și problema getchar() problema așa cum a fost demonstrată aici.

Comentarii

  • Getch-ul este probabil mai important pentru @Boxiom. –  > Por Matthew Carlson.
  • Acest răspuns mi-a rezolvat problema, așa că mulțumesc Christophe. Dar aș dori să adaug că „#include <sys/ioctl.h>” a fost necesar pentru ca funcția lui Morgan McGuire să funcționeze în programul meu datorită utilizării FIONREAD, iar acest lucru nu este menționat în mod explicit ca fiind necesar pe pagina web unde se găsește codul său. –  > Por user251563.
  • stropts.h nu există un astfel de fișier sau director. se pare că linux nu mai suportă acest lucru –  > Por elig.
  • @elig într-adevăr, se pare că există unele schimbări recente în lumea linux: bugs.debian.org/cgi-bin/bugreport.cgi?bug=954552 – Există deja câteva răspunsuri recente despre probleme similare cu stropts.h pe SO: poate găsiți unul potrivit, poate deschideți unul nou. Ar fi grozav să adăugați un comentariu dacă găsiți o soluție pentru cei care ar putea avea aceeași problemă cu aceeași distribuție. –  > Por Christophe.
PBS

O soluție compactă bazată pe răspunsul lui Christophe este următoarea

#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;
}

Spre deosebire de acel răspuns, aceasta nu va lăsa terminalul într-o stare ciudată după ce programul a ieșit. Cu toate acestea, lasă totuși caracterele așezate în bufferul de intrare, astfel încât tasta care a fost apăsată va apărea în mod neplăcut pe următoarea linie de prompt.

O altă soluție care rezolvă această problemă este

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;
}

Utilizarea se face după cum urmează

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

Acum, orice caracter tastat între executarea primei și ultimei linii nu va mai apărea în terminal. Cu toate acestea, dacă ieșiți cu Ctrl+C, terminalul este lăsat într-o stare ciudată. (Sigh)

Comentarii

  • Acest lucru funcționează pentru a determina dacă a fost apăsată o tastă, dar nu oferă nicio modalitate de a obține caracterul corespunzător (dacă folosiți getc(), rămâneți în continuare să așteptați ca utilizatorul să apese Enter). Vedeți această soluție dacă doriți să așteptați apăsarea unei taste și să obțineți imediat caracterul corespunzător: stackoverflow.com/a/912796/544099 –  > Por ishmael.
Prof. Falken

În timp ce utilizarea ncurses este echivalentă din punct de vedere funcțional cu Turbo C „conio.h” API, o soluție mai completă este utilizarea unei implementări conio, așa cum poate fi găsită aici.

O descărcați și o utilizați în programul dumneavoastră pentru o implementare foarte completă a interfeței conio, pe Linux. (Sau OSX.) Scris de Ron Burkey.

Comentarii

  • O notă de pe pagina Turbo C afirmă că amestecarea funcționalității bazate pe fluxuri cu conio probabil nu va funcționa. Acest lucru elimină principalul motiv pentru care cineva ar putea dori să o folosească, deoarece exemplul lui OP nu este garantat să funcționeze. –  > Por Thomas Dickey.
orlov_dumitru

Dacă folosiți Linux, am găsit această soluție prin care vă puteți crea propria bibliotecă locală:

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

în interiorul main.cpp am creat o instanță:

#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;
}