How do I wait for a keystroke interrupt with a syscall on Linux?

前端 未结 1 485
小蘑菇
小蘑菇 2021-01-14 11:12

I want to receive an interrupt when the user presses a special keystroke like F1-12 in my program, which is written in nasm. I simply need to wait for a function keystroke a

相关标签:
1条回答
  • 2021-01-14 11:24

    The necessary code for this is rather complicated; I eventually figured out how to check for F1 in C with raw ioctl, read, and write. The translation to nasm should be straightforward if you're familiar with assembly and Linux syscalls.

    It's not exactly what you want, in that it only checks for F1, not the rest of them. F1's sequence is 0x1b, 0x4f, 0x50. You can find other sequences with od -t x1 and pressing the key. For example, F2 is 0x1b, 0x4f, 0x51.

    The basic idea is that we get the current terminal attributes, update them to be raw (cfmakeraw), and then set them back. The ioctl syscall is used for this.

    On a terminal in raw mode, read() will get any character(s) the user has typed, unlike the "cooked" mode where the kernel does line-editing with backspace and control-u until the user submits the line by pressing enter or control-d (EOF).

    #include <unistd.h>
    #include <sys/ioctl.h>
    #include <termios.h>
    
    struct ktermios {
        tcflag_t c_iflag;
        tcflag_t c_oflag;
        tcflag_t c_cflag;
        tcflag_t c_lflag;
        cc_t c_line;
        cc_t c_cc[19];
    };
    
    int getch() {
        unsigned char c;
        read(0, &c, sizeof(c));
        return c;
    }
    
    int main(int argc, char *argv[]) {
        struct ktermios orig, new;
        ioctl(0, TCGETS, &orig);
        ioctl(0, TCGETS, &new);   // or more simply  new = orig;
    
        // from cfmakeraw documentation
        new.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON);
        new.c_oflag &= ~OPOST;
        new.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
        new.c_cflag &= ~(CSIZE | PARENB);
        new.c_cflag |= CS8;
    
        ioctl(0, TCSETS, &new);
    
        while (1) {
            if (getch() == 0x1b && getch() == 0x4f && getch() == 0x50) {
                break;
            }
        }
    
        write(1, "Got F1!\n", 8);
        ioctl(0, TCSETS, &orig);    // restore original settings before exiting!
        return 0;
    }
    

    I based this on this answer, which was very helpful.

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