How to detect key presses in a Linux C GUI program without prompting the user?

前端 未结 4 1851
一生所求
一生所求 2020-12-04 00:24

how to detect a keyboard event in C without prompting the user in linux? That is the program running should terminate by pressing any key. can anyone please help with this?<

相关标签:
4条回答
  • 2020-12-04 01:03

    You have to modify terminal settings using termios. See Stevens & Rago 2nd Ed 'Advanced Programming in the UNIX Environment' it explains why tcsetattr() can return successfuly without having set all terminal characteristcs, and why you see what looks to be redundant calls to tcsetattr().

    This is ANSI C in 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 */
    }
    

    The nanosleep call is there to prevent the code from gobbling up system resources. You could call nice() and not use nanosleep(). All this does is sit and wait for a keystroke, then exit.

    0 讨论(0)
  • 2020-12-04 01:15

    If you want to do that in a graphical application, you should use some libraries to do this.

    Such a simple task can be easily done with whatever library (even low level ones like Xlib).

    Just choose one and look for a tutorial that shows how to handle keyboard events.

    0 讨论(0)
  • 2020-12-04 01:17

    no way with ANSI C. Look at ncurses lib.

    0 讨论(0)
  • 2020-12-04 01:17

    Here’s code from /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;
            }
    }
    

    At a minimum, you have to get out of ICANON mode before your select(2) syscall or your FIONREAD ioctl will work.

    I have an ancient, 20-year-old perl4 program that clears CBREAK and ECHO mode this way. It is doing curses stuff without resorting to the curses library:

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

    And it elsewhere says that for POSIX,

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

    RE-translation back into the original C is left as an exercise for the reader, because I can't remember where the 20-years-ago-me snagged it from originally. :(

    Once you're out of ICANON mode on the tty, now your select(2) syscall works properly again. When select's read mask returns that that descriptor is ready, then you do a FIONREAD ioctl to discover exactly how many bytes are waiting for you on that file descriptor. Having got that, you can do a read(2) syscall for just that many bytes, preferably on an O_NONBLOCK descriptor, although by now that should no longer be necessary.

    Hm, here’s a foreboding note in /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.
    

    If you do a recursive grep for \b(TIOC[SG]ET[NP]|TC[SG]ET[SA]|tc[sg]etattr)\b on the non-kernel portions of /usr/src/, you should find stuff you can use. For example:

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

    I would look at /usr/src/usr.bin/less/screen.c, down in the raw_mode() function. Riddled with ifdefs though it is in a quest for portability, that looks like the cleanest code for what you want to do. There’s also stuff lurking down in GNU.

    OH MY, look in /usr/src/gnu/usr.bin/perl/h2pl/cbreak.pl! That must be my old code that I posted above. Interesting that it’s trickled out to every src system in the world. Scary, too, since it is twenty years out of date. Gosh, it's weird to see echoes of a younger self. Really hard to remember such particulars from 20 years ago.

    I also see in /usr/src/lib/libcurses/term.h this line:

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

    in a bunch of ifdefs that are trying to infer termio or termios availability.

    This should be enough to get you started.

    0 讨论(0)
提交回复
热议问题