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é?
Répondre
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.
aucun moyen avec ANSI C. Regardez ncurses lib.
Hmm. Voir le code de termios, C droit, plus quelques appels UNIX. –
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.
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.
Je peux confirmer cela pour travailler sur OS X 10.6. – febeling
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? –
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. –
Ensuite, la question suivante serait: quel cadre GUI utilisez-vous? – casablanca