2010-11-27 28 views
2

comment détecter un événement de clavier dans C sans inviter l'utilisateur dans linux? C'est le programme en cours d'exécution devrait se terminer en appuyant sur une touche. quelqu'un peut-il aider s'il vous plaît avec ceci?Comment détecter les pressions de touches dans un programme d'interface graphique Linux C sans que l'utilisateur ne soit invité?

+2

Voulez-vous cela dans un terminal ou dans un programme GUI? Ou peut-être un type de programme framebuffer plein écran, comme quelque chose en utilisant SDL? Ou autre chose? –

+1

Je le veux pour un programme GUI comme un économiseur d'écran. dès que j'appuie sur une touche, l'application devrait se terminer. –

+1

Ensuite, la question suivante serait: quel cadre GUI utilisez-vous? – casablanca

Répondre

1

Si vous voulez faire cela dans une application graphique, vous devez utiliser des bibliothèques pour cela.

Une tâche aussi simple peut être facilement réalisée avec n'importe quelle bibliothèque (même de niveau inférieur comme Xlib). Choisissez-en un et cherchez un tutoriel qui montre comment gérer les événements du clavier.

1

aucun moyen avec ANSI C. Regardez ncurses lib.

+0

Hmm. Voir le code de termios, C droit, plus quelques appels UNIX. –

1

est ici le code de /usr/src/bin/stty/key.c:

f_cbreak(struct info *ip) 
{ 

     if (ip->off) 
       f_sane(ip); 
     else { 
       ip->t.c_iflag |= BRKINT|IXON|IMAXBEL; 
       ip->t.c_oflag |= OPOST; 
       ip->t.c_lflag |= ISIG|IEXTEN; 
       ip->t.c_lflag &= ~ICANON; 
       ip->set = 1; 
     } 
} 

Au minimum, il faut sortir du mode ICANON avant select(2) syscall ou votre FIONREAD ioctl travaillerez.

J'ai un ancien programme perl4 de 20 ans qui efface le mode CBREAK et ECHO de cette façon. Il fait malédictions des choses sans avoir recours à la malédiction bibliothèque:

sub BSD_cbreak { 
    local($on) = shift; 
    local(@sb); 
    local($sgttyb); 
    # global $sbttyb_t 

    $sgttyb_t = &sgttyb'typedef() unless defined $sgttyb_t; 

    # native BSD stuff by author (tsc) 

    ioctl(TTY,&TIOCGETP,$sgttyb) 
     || die "Can't ioctl TIOCGETP: $!"; 

    @sb = unpack($sgttyb_t,$sgttyb); 
    if ($on) { 
     $sb[&sgttyb'sg_flags] |= &CBREAK; 
     $sb[&sgttyb'sg_flags] &= ~&ECHO; 
    } else { 
     $sb[&sgttyb'sg_flags] &= ~&CBREAK; 
     $sb[&sgttyb'sg_flags] |= &ECHO; 
    } 
    $sgttyb = pack($sgttyb_t,@sb); 
    ioctl(TTY,&TIOCSETN,$sgttyb) 
      || die "Can't ioctl TIOCSETN: $!"; 
} 


sub SYSV_cbreak { 
    # SysV code contributed by Jeff Okamoto <[email protected]> 

    local($on) = shift; 
    local($termio,@termio); 
    # global termio_t ??? 

    $termio_t = &termio'typedef() unless defined $termio_t; 

    ioctl(TTY,&TCGETA,$termio) 
     || die "Can't ioctl TCGETA: $!"; 

    @termio = unpack($termio_t, $termio); 
    if ($on) { 
     $termio[&termio'c_lflag] &= ~(&ECHO | &ICANON); 
     $termio[&termio'c_cc + &VMIN] = 1; 
     $termio[&termio'c_cc + &VTIME] = 1; 
    } else { 
     $termio[&termio'c_lflag] |= (&ECHO | &ICANON); 

     # In HP-UX, it appears that turning ECHO and ICANON back on is 
     # sufficient to re-enable cooked mode. Therefore I'm not bothering 
     # to reset VMIN and VTIME (VEOF and VEOL above). This might be a 
     # problem on other SysV variants. 

    } 
    $termio = pack($termio_t, @termio); 
    ioctl(TTY, &TCSETA, $termio) 
     || die "Can't ioctl TCSETA: $!"; 

} 


sub POSIX_cbreak { 
    local($on) = shift; 
    local(@termios, $termios, $bitmask); 

    # "file statics" for package cbreak: 
    #  $savebits, $save_vtime, $save_vmin, $is_on 

    $termios_t = &termios'typedef() unless defined $termios_t; 
    $termios = pack($termios_t,()); # for Sun SysVr4, which dies w/o this 

    ioctl(TTY,&$GETIOCTL,$termios) 
     || die "Can't ioctl GETIOCTL ($GETIOCTL): $!"; 

    @termios = unpack($termios_t,$termios); 

    $bitmask = &ICANON | &IEXTEN | &ECHO; 
    if ($on && $cbreak'ison == 0) { 
     $cbreak'ison = 1; 
     $cbreak'savebits = $termios[&termios'c_lflag] & $bitmask; 
     $termios[&termios'c_lflag] &= ~$bitmask; 
     $cbreak'save_vtime = $termios[&termios'c_cc + &VTIME]; 
     $termios[&termios'c_cc + &VTIME] = 0; 
     $cbreak'save_vmin = $termios[&termios'c_cc + &VMIN]; 
     $termios[&termios'c_cc + &VMIN] = 1; 
    } elsif (!$on && $cbreak'ison == 1) { 
     $cbreak'ison = 0; 
     $termios[&termios'c_lflag] |= $cbreak'savebits; 
     $termios[&termios'c_cc + &VTIME] = $cbreak'save_vtime; 
     $termios[&termios'c_cc + &VMIN] = $cbreak'save_vmin; 
    } else { 
     return 1; 
    } 
    $termios = pack($termios_t,@termios); 
    ioctl(TTY,&$SETIOCTL,$termios) 
     || die "Can't ioctl SETIOCTL ($SETIOCTL): $!"; 
} 

sub DUMB_cbreak { 
    local($on) = shift; 

    if ($on) { 
     system("stty cbreak -echo"); 
    } else { 
     system("stty -cbreak echo"); 
    } 
} 

Et il est dit ailleurs que pour Posix,

($GETIOCTL, $SETIOCTL) = (TIOCGETA, TIOCSETA); 

RE-retraduction dans le C d'origine est laissé comme un exercice pour le lecteur , parce que je ne me souviens pas où il y a 20 ans. :(

Une fois que vous êtes hors du mode ICANON sur le téléscripteur, maintenant votre select(2) syscall fonctionne à nouveau correctement. Lorsque select « s lecture renvoie masque ce descripteur est prêt, alors vous faites un FIONREAD ioctl pour découvrir exactement combien d'octets vous attendent sur ce descripteur de fichier. Après avoir obtenu cela, vous pouvez faire un read(2) syscall pour seulement que beaucoup d'octets, de préférence sur un descripteur O_NONBLOCK, bien que maintenant cela ne devrait pas être plus nécessaire.

Hm, voici pressenti notez dans /usr/src/usr.bin/vi/cl/README.signal:

Run in cbreak mode. There are two problems in this area. First, the 
    current curses implementations (both System V and Berkeley) don't give 
    you clean cbreak modes. For example, the IEXTEN bit is left on, turning 
    on DISCARD and LNEXT. To clarify, what vi WANTS is 8-bit clean, with 
    the exception that flow control and signals are turned on, and curses 
    cbreak mode doesn't give you this. 

    We can either set raw mode and twiddle the tty, or cbreak mode and 
    twiddle the tty. I chose to use raw mode, on the grounds that raw 
    mode is better defined and I'm less likely to be surprised by a curses 
    implementation down the road. The twiddling consists of setting ISIG, 
    IXON/IXOFF, and disabling some of the interrupt characters (see the 
    comments in cl_init.c). This is all found in historic System V (SVID 
    3) and POSIX 1003.1-1992, so it should be fairly portable. 

Si vous faites un grep récursif pour \b(TIOC[SG]ET[NP]|TC[SG]ET[SA]|tc[sg]etattr)\b sur les parties non-noyau de /usr/src/, vous devriez trouver des choses que vous pouvez utiliser. Par exemple:

% grep -Pr '\b(TIOC[SG]ET[NP]|TC[SG]ET[SA]|tc[sg]etattr)\b' /usr/src/{{s,}bin,usr.{s,}bin,etc,gnu} 

Je regardais /usr/src/usr.bin/less/screen.c, dans la fonction raw_mode(). Criblé de ifdef s bien qu'il soit dans une quête de portabilité, cela ressemble au code le plus propre pour ce que vous voulez faire. Il y a aussi des choses cachées dans GNU.

OH MY, regardez dans /usr/src/gnu/usr.bin/perl/h2pl/cbreak.pl! Ce doit être mon ancien code que j'ai posté ci-dessus. Intéressant que cela a coulé à tous les systèmes de SRC dans le monde. Effrayant, aussi, puisqu'il est vingt ans périmés. Gosh, c'est bizarre de voir des échos d'un moi plus jeune. Vraiment difficile de se souvenir de tels détails d'il y a 20 ans.

Je vois aussi dans cette ligne /usr/src/lib/libcurses/term.h:

#define tcgetattr(fd, arg) ioctl(fd, TCGETA, arg) 

dans un tas de ifdef s qui tentent de déduire termio ou termios disponibilité.

Cela devrait être suffisant pour vous aider à démarrer.

6

Vous devez modifier les paramètres du terminal à l'aide de termios. Voir Stevens & Rago 2nd Ed 'Programmation avancée dans l'environnement UNIX' explique pourquoi tcsetattr() peut retourner avec succès sans avoir défini toutes les caractéristiques du terminal, et pourquoi vous voyez ce qui ressemble à des appels redondants à tcsetattr().

C'est ANSI C sous UNIX:

#include <sys/types.h> 
#include <sys/time.h> 
#include <stdlib.h> 
#include <stdio.h> 
#include <string.h> 
#include <unistd.h> 
#include <termios.h> 
#include <errno.h> 

int checktty(struct termios *p, int term_fd) 
{ 
    struct termios ck; 
    return (
     tcgetattr(term_fd, &ck) == 0 && 
     (p->c_lflag == ck.c_lflag) && 
     (p->c_cc[VMIN] == ck.c_cc[VMIN]) && 
     (p->c_cc[VTIME] == ck.c_cc[VMIN]) 
    ); 
} 


int 
keypress(int term_fd) 
{ 
    unsigned char ch; 
    int retval=read(term_fd, &ch, sizeof ch); 
    return retval; 
} 

int /* TCSAFLUSH acts like fflush for stdin */ 
flush_term(int term_fd, struct termios *p) 
{ 
    struct termios newterm; 
    errno=0; 
    tcgetattr(term_fd, p); /* get current stty settings*/ 

    newterm = *p; 
    newterm.c_lflag &= ~(ECHO | ICANON); 
    newterm.c_cc[VMIN] = 0; 
    newterm.c_cc[VTIME] = 0; 

    return( 
     tcgetattr(term_fd, p) == 0 && 
     tcsetattr(term_fd, TCSAFLUSH, &newterm) == 0 && 
     checktty(&newterm, term_fd) != 0 
    ); 
} 
void 
term_error(void) 
{ 
    fprintf(stderr, "unable to set terminal characteristics\n"); 
    perror("");             
    exit(1);             
} 


void 
wait_and_exit(void) 
{ 
    struct timespec tsp={0,500}; /* sleep 500 usec (or likely more) */ 
    struct termios attr; 
    struct termios *p=&attr; 
    int keepon=0; 
    int term_fd=fileno(stdin); 

    fprintf(stdout, "press any key to continue:"); 
    fflush(stdout); 
    if(!flush_term(term_fd, p)) 
     term_error(); 
    for(keepon=1; keepon;) 
    { 
     nanosleep(&tsp, NULL); 
     switch(keypress(term_fd)) 
     { 
       case 0: 
       default: 
       break; 
      case -1: 
       fprintf(stdout, "Read error %s", strerror(errno)); 
       exit(1); 
       break; 
      case 1:  /* time to quit */ 
       keepon=0; 
       fprintf(stdout, "\n"); 
       break;     
     } 
    } 
    if(tcsetattr(term_fd, TCSADRAIN, p) == -1 && 
      tcsetattr(term_fd, TCSADRAIN, p) == -1) 
      term_error(); 
    exit(0); 
} 

int main() 
{ 
     wait_and_exit(); 
     return 0; /* never reached */ 
} 

L'appel nanosleep est là pour empêcher le code de gober des ressources système. Vous pouvez appeler nice() et ne pas utiliser nanosleep(). Tout cela est assis et attendez une frappe, puis quittez.

+0

Je peux confirmer cela pour travailler sur OS X 10.6. – febeling