You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何跨终端监控交互式C程序的按键输入?现有kbhit方案无效

Hey there! Let's break down why your current observer setup isn't catching keystrokes from another terminal, then walk through a few practical solutions to fix this.

Why Your Current Code Fails

Your existing kbhit() and select() logic is only listening to your observer program's own standard input (stdin, file descriptor 0). Every terminal window is a separate TTY device (think /dev/pts/0, /dev/pts/1, etc.), and their input streams are completely isolated. So your observer can only see keys pressed in its own terminal, not the one running program1.


Solution 1: Use a Named Pipe (FIFO)

This is the easiest approach if you can modify program1. The idea is to have program1 write every keystroke to a shared pipe, and your observer watches that pipe for new data.

Step 1: Create the FIFO

Run this command in your terminal to make a shared pipe:

mkfifo /tmp/key_pipe

Step 2: Update program1 to Write Keystrokes to the Pipe

Modify your interactive program to send each valid digit to the pipe:

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>

int main() {
    // Open the pipe for writing
    int pipe_fd = open("/tmp/key_pipe", O_WRONLY);
    if (pipe_fd == -1) {
        perror("Failed to open pipe");
        return 1;
    }

    char ch;
    printf("Enter digits (0-9):\n");
    while ((ch = getchar()) != EOF) {
        // Only send valid 0-9 digits
        if (ch >= '0' && ch <= '9') {
            write(pipe_fd, &ch, 1);
        }
    }

    close(pipe_fd);
    return 0;
}

Step 3: Rewrite the Observer to Watch the Pipe

Replace your observer's logic to listen to the pipe instead of its own stdin:

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/select.h>

void press(char key) {
    printf("Detected key press: %c !!!\n", key);
}

int main() {
    // Open the pipe in non-blocking read mode
    int pipe_fd = open("/tmp/key_pipe", O_RDONLY | O_NONBLOCK);
    if (pipe_fd == -1) {
        perror("Failed to open pipe");
        return 1;
    }

    fd_set read_fds;
    char ch;
    while (1) {
        FD_ZERO(&read_fds);
        FD_SET(pipe_fd, &read_fds);

        // Wait for data to arrive in the pipe
        int status = select(pipe_fd + 1, &read_fds, NULL, NULL, NULL);
        if (status > 0 && FD_ISSET(pipe_fd, &read_fds)) {
            ssize_t bytes_read = read(pipe_fd, &ch, 1);
            if (bytes_read > 0) {
                press(ch);
            }
        }
    }

    close(pipe_fd);
    return 0;
}

How to Use

  1. Launch your observer first: ./observer_prog
  2. Open a new terminal and run program1: ./program1
  3. Type digits into program1's terminal—your observer will instantly detect and report each keystroke.

Solution 2: Monitor the Target Terminal's TTY Device

If you can't modify program1, you can directly watch the TTY device that program1 is running in.

Step 1: Find program1's TTY Path

In the terminal running program1, run:

tty

You'll get a path like /dev/pts/2—this is the device we'll monitor.

Step 2: Update the Observer to Watch the Target TTY

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/select.h>
#include <termios.h>

void press(char key) {
    printf("Detected key press: %c !!!\n", key);
}

int main() {
    // Replace this with the TTY path from the step above
    const char* target_tty = "/dev/pts/2";
    // Open the TTY in non-blocking read mode
    int tty_fd = open(target_tty, O_RDONLY | O_NONBLOCK);
    if (tty_fd == -1) {
        perror("Failed to open target TTY");
        return 1;
    }

    // Adjust TTY settings to read keystrokes in real-time
    struct termios tty_attr;
    tcgetattr(tty_fd, &tty_attr);
    tty_attr.c_lflag &= ~(ICANON | ECHO); // Disable canonical mode and echo
    tty_attr.c_cc[VMIN] = 1; // Wait for at least 1 character
    tty_attr.c_cc[VTIME] = 0; // No timeout
    tcsetattr(tty_fd, TCSANOW, &tty_attr);

    fd_set read_fds;
    char ch;
    while (1) {
        FD_ZERO(&read_fds);
        FD_SET(tty_fd, &read_fds);

        int status = select(tty_fd + 1, &read_fds, NULL, NULL, NULL);
        if (status > 0 && FD_ISSET(tty_fd, &read_fds)) {
            ssize_t bytes_read = read(tty_fd, &ch, 1);
            if (bytes_read > 0) {
                press(ch);
            }
        }
    }

    close(tty_fd);
    return 0;
}

Notes

  • You might need to run the observer with sudo to access the target TTY (unless both terminals are owned by the same user with proper permissions).
  • This will capture all input to the target terminal (like Enter keys), so you'll want to add logic to filter for just 0-9 digits if needed.

Solution 3: Use a UNIX Domain Socket

For more robust communication (especially if you might have multiple observers or need to handle disconnections), a UNIX domain socket is a great choice.

Step 1: Update program1 to Send Keystrokes to the Socket

#include <stdio.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <string.h>

#define SOCKET_PATH "/tmp/key_socket"

int main() {
    int sock_fd = socket(AF_UNIX, SOCK_STREAM, 0);
    if (sock_fd == -1) {
        perror("Failed to create socket");
        return 1;
    }

    struct sockaddr_un addr;
    memset(&addr, 0, sizeof(addr));
    addr.sun_family = AF_UNIX;
    strncpy(addr.sun_path, SOCKET_PATH, sizeof(addr.sun_path) - 1);

    if (connect(sock_fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
        perror("Failed to connect to observer");
        close(sock_fd);
        return 1;
    }

    char ch;
    printf("Enter digits (0-9):\n");
    while ((ch = getchar()) != EOF) {
        if (ch >= '0' && ch <= '9') {
            send(sock_fd, &ch, 1, 0);
        }
    }

    close(sock_fd);
    return 0;
}

Step 2: Rewrite the Observer to Listen on the Socket

#include <stdio.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <sys/select.h>
#include <string.h>

#define SOCKET_PATH "/tmp/key_socket"

void press(char key) {
    printf("Detected key press: %c !!!\n", key);
}

int main() {
    // Clean up any existing socket file
    unlink(SOCKET_PATH);

    int sock_fd = socket(AF_UNIX, SOCK_STREAM, 0);
    if (sock_fd == -1) {
        perror("Failed to create socket");
        return 1;
    }

    struct sockaddr_un addr;
    memset(&addr, 0, sizeof(addr));
    addr.sun_family = AF_UNIX;
    strncpy(addr.sun_path, SOCKET_PATH, sizeof(addr.sun_path) - 1);

    if (bind(sock_fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
        perror("Failed to bind socket");
        close(sock_fd);
        return 1;
    }

    if (listen(sock_fd, 1) == -1) {
        perror("Failed to listen for connections");
        close(sock_fd);
        return 1;
    }

    // Accept connection from program1
    int client_fd = accept(sock_fd, NULL, NULL);
    if (client_fd == -1) {
        perror("Failed to accept connection");
        close(sock_fd);
        return 1;
    }

    fd_set read_fds;
    char ch;
    while (1) {
        FD_ZERO(&read_fds);
        FD_SET(client_fd, &read_fds);

        int status = select(client_fd + 1, &read_fds, NULL, NULL, NULL);
        if (status > 0 && FD_ISSET(client_fd, &read_fds)) {
            ssize_t bytes_read = recv(client_fd, &ch, 1, 0);
            if (bytes_read > 0) {
                press(ch);
            } else {
                // Reconnect if program1 restarts
                close(client_fd);
                client_fd = accept(sock_fd, NULL, NULL);
                if (client_fd == -1) {
                    perror("Failed to accept new connection");
                    break;
                }
            }
        }
    }

    close(client_fd);
    close(sock_fd);
    unlink(SOCKET_PATH);
    return 0;
}

Quick Recommendation

  • If you can modify program1, go with the named pipe—it's simple, requires minimal code changes, and works reliably.
  • If you can't touch program1, the TTY monitoring approach is your best bet, just be mindful of permissions and input filtering.
  • For more complex setups, the UNIX domain socket offers better reliability and flexibility.

内容的提问来源于stack exchange,提问作者Pert8S

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 07:31:49