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
相关产品推荐
相关产品推荐

