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

Linux下PTY自定义按键映射与TTY-PTY通信实现咨询

PTY按键捕获与映射实现指南

1. 终端属性初始化(禁用默认信号与规范模式)

要捕获原始按键并禁用系统默认的信号触发,需修改当前TTY和PTY主端的终端属性,核心是关闭以下标志:

  • ICANON:禁用规范模式,让输入字符立即被读取,无需等待回车
  • ECHO:避免按键直接回显,由程序手动控制输出
  • ISIG:阻止Ctrl+Z/Ctrl+C等按键直接触发系统信号

代码示例:

#include <termios.h>
#include <unistd.h>

void setup_raw_mode(int fd) {
    struct termios term;
    tcgetattr(fd, &term);
    // 清除目标标志位
    term.c_lflag &= ~(ICANON | ECHO | ISIG);
    // 设置最小读取字符数为1,超时为0(立即返回)
    term.c_cc[VMIN] = 1;
    term.c_cc[VTIME] = 0;
    tcsetattr(fd, TCSAFLUSH, &term);
}

2. 使用forkpty创建PTY环境

调用forkpty快速创建主从PTY对,子进程会自动关联PTY从端作为控制终端。主进程负责输入输出的转发与按键映射:

#include <pty.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/wait.h>

int main() {
    int master_fd;
    pid_t pid = forkpty(&master_fd, NULL, NULL, NULL);
    if (pid == -1) {
        perror("forkpty failed");
        exit(EXIT_FAILURE);
    }

    if (pid == 0) {
        // 子进程启动目标程序(示例为bash)
        execl("/bin/bash", "bash", NULL);
        perror("execl failed");
        exit(EXIT_FAILURE);
    }

    // 主进程初始化终端模式
    setup_raw_mode(STDIN_FILENO);
    setup_raw_mode(master_fd);

    // 后续多路复用处理逻辑...
}

3. 多路复用与按键映射逻辑

用epoll实现高效IO多路复用,同时监听用户输入(当前TTY)和子进程输出(PTY主端):

  • 监听STDIN_FILENO:读取用户按键,将Ctrl+Z(十六进制0x1a)替换为Ctrl+.(十六进制0x0e)后写入PTY主端;其他按键直接转发
  • 监听master_fd:读取子进程输出,直接回显到当前TTY

代码示例:

#include <sys/epoll.h>
#include <string.h>

#define MAX_EVENTS 2

int main() {
    // ... 省略前面forkpty和setup_raw_mode的代码 ...

    int epoll_fd = epoll_create1(0);
    if (epoll_fd == -1) {
        perror("epoll_create1 failed");
        exit(EXIT_FAILURE);
    }

    struct epoll_event events[MAX_EVENTS];
    struct epoll_event stdin_event = {.events = EPOLLIN, .data.fd = STDIN_FILENO};
    struct epoll_event pty_event = {.events = EPOLLIN, .data.fd = master_fd};

    epoll_ctl(epoll_fd, EPOLL_CTL_ADD, STDIN_FILENO, &stdin_event);
    epoll_ctl(epoll_fd, EPOLL_CTL_ADD, master_fd, &pty_event);

    char buf[256];
    while (1) {
        int nfds = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);
        if (nfds == -1) {
            perror("epoll_wait failed");
            break;
        }

        for (int i = 0; i < nfds; i++) {
            if (events[i].data.fd == STDIN_FILENO) {
                ssize_t n = read(STDIN_FILENO, buf, sizeof(buf));
                if (n <= 0) break;

                // 执行按键映射
                for (ssize_t j = 0; j < n; j++) {
                    if (buf[j] == 0x1a) buf[j] = 0x0e;
                }

                write(master_fd, buf, n);
            } else if (events[i].data.fd == master_fd) {
                ssize_t n = read(master_fd, buf, sizeof(buf));
                if (n <= 0) break;
                write(STDOUT_FILENO, buf, n);
            }
        }
    }

    // 程序退出前恢复终端原始属性
    struct termios term;
    tcgetattr(STDIN_FILENO, &term);
    term.c_lflag |= (ICANON | ECHO | ISIG);
    tcsetattr(STDIN_FILENO, TCSAFLUSH, &term);

    waitpid(pid, NULL, 0);
    close(master_fd);
    close(epoll_fd);
    return 0;
}

4. 关键注意事项

  • 终端属性恢复:必须在程序退出前恢复当前TTY的原始属性,否则终端会处于无回显、无规范模式的混乱状态
  • 按键编码验证:不同终端的Ctrl+按键编码可能存在差异,可通过od -t x1工具测试:输入目标按键后按Ctrl+D,查看输出的十六进制值
  • 信号处理:主进程需处理SIGCHLD信号,避免子进程变为僵尸进程
  • 错误处理:所有系统调用(read/write/epoll_ctl等)都要添加错误处理,提升程序健壮性

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.06 14:13:09