Как обнаружить нажатия клавиш в программе Linux C GUI без запроса пользователя? - PullRequest
2 голосов
/ 27 ноября 2010

как обнаружить событие клавиатуры в C без запроса пользователя в Linux? То есть запущенная программа должна завершиться нажатием любой клавиши. Может кто-нибудь, пожалуйста, помогите с этим?

Ответы [ 4 ]

6 голосов
/ 28 ноября 2010

Вы должны изменить настройки терминала, используя termios.См. Stevens & Rago 2nd Ed «Расширенное программирование в среде UNIX», в котором объясняется, почему tcsetattr () может возвращать успешно, без установки всех характеристик терминала, и почему вы видите то, что выглядит как избыточные вызовы tcsetattr ().

Это ANSI C в 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 */
}

Вызов nanosleep предназначен для того, чтобы код не сожрал системные ресурсы.Вы можете вызвать nice () и не использовать nanosleep ().Все, что нужно сделать, это сесть и ждать нажатия клавиши, а затем выйти.

1 голос
/ 28 ноября 2010

никак с ANSI C. Посмотрите на ncurses lib.

1 голос
/ 28 ноября 2010

Вот код от /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;
        }
}

Как минимум, вы должны выйти из режима ICANON, прежде чем ваш select(2) системный вызов или FIONREAD ioctl сработает.

У меня есть древняя 20-летняя программа perl4, которая таким образом очищает режимы CBREAK и ECHO. Он делает вещи с проклятиями, не прибегая к библиотеке проклятий:

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 <okamoto@hpcc25.corp.hp.com>

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

А в другом месте сказано, что для POSIX,

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

Повторный перевод обратно в исходный C оставлен в качестве упражнения для читателя, потому что я не могу вспомнить, откуда 20-летней давности я его поймал. (

После того как вы вышли из режима ICANON на tty, теперь ваш системный вызов select(2) снова работает правильно. Когда маска чтения select возвращает, что этот дескриптор готов, тогда вы делаете FIONREAD ioctl, чтобы точно узнать, сколько байтов ожидает вас в этом дескрипторе файла. Получив это, вы можете сделать системный вызов read(2) только для такого количества байтов, предпочтительно для дескриптора O_NONBLOCK, хотя к настоящему времени это больше не требуется.

Хм, вот примечание предчувствия в /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.

Если вы делаете рекурсивный grep для \b(TIOC[SG]ET[NP]|TC[SG]ET[SA]|tc[sg]etattr)\b для неядерных частей /usr/src/, вы должны найти материал, который вы можете использовать. Например:

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

Я бы посмотрел на /usr/src/usr.bin/less/screen.c, вниз в функции raw_mode(). Загаданный ifdef s, хотя он находится в поиске переносимости, это выглядит как самый чистый код для того, что вы хотите сделать. В GNU также есть что-то скрытое.

О, МОЙ , посмотрите /usr/src/gnu/usr.bin/perl/h2pl/cbreak.pl! Это должен быть мой старый код, который я разместил выше. Интересно, что это применимо ко всем системам src в мире. Страшно тоже, потому что - двадцать лет назад. Черт возьми, странно видеть отголоски молодого себя. Действительно трудно вспомнить такие подробности 20 лет назад.

Я также вижу в /usr/src/lib/libcurses/term.h эту строку:

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

в связке ifdef с, которые пытаются определить termio или termios доступность.

Этого должно быть достаточно, чтобы начать работу.

1 голос
/ 27 ноября 2010

Если вы хотите сделать это в графическом приложении, вы должны использовать некоторые библиотеки для этого.

Такую простую задачу можно легко выполнить с любой библиотекой (даже низкоуровневой, такой как Xlib).1003 *

Просто выберите один и найдите учебник, в котором показано, как обрабатывать события клавиатуры.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...