TAB 和 KEY_UP 等键在 PTY 程序中无法按预期工作(例如 KEY_UP 变为“^[[A”)

Keys like TAB and KEY_UP don't work as expected (e.g. KEY_UP becomes "^[[A") in PTY program

我有一个 pty 代码示例,(可能是最流行的 pty 示例)我正在尝试使用它在从机中启动 sh [shell terminal] 但热键如 cd 或 key up [最后一个命令]不工作[什么都不做] 我用 gcc -o pty.o pty.c 编译它 和 运行 ./pty.o "sh" 但是当我尝试使用 up 键时,它只是打印 ^[[A 和 tab do tab indent 而不是建议目录选项。 代码在 http://www.rkoucha.fr/tech_corner/pty_pdip.html :

在 mypty3 下:

We can make mypty2 more generic in order to be able to execute any program behind the pty (slave side). In mypty3, the father process writes all the data from its standard input to the master side of the pty and writes all the data from the master side of the pty to its standard output. The child process behaves the same as in mypty2 but executes an interactive program along with its parameters passed as arguments to the program. We can note the calls to setsid() and ioctl(TIOCSCTTY) to make the pty be the control terminal of the executed program. We can also note the closing of the fds file descriptor which becomes useless after the calls to dup().

 #define _XOPEN_SOURCE 600
#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <stdio.h>
#define __USE_BSD
#include <termios.h>
#include <sys/select.h>
#include <sys/ioctl.h>
#include <string.h>


int main(int ac, char *av[])
{
int fdm, fds;
int rc;
char input[150];

// Check arguments
if (ac <= 1)
{
fprintf(stderr, "Usage: %s program_name [parameters]\n", av[0]);
exit(1);
}

fdm = posix_openpt(O_RDWR);
if (fdm < 0)
{
fprintf(stderr, "Error %d on posix_openpt()\n", errno);
return 1;
}

rc = grantpt(fdm);
if (rc != 0)
{
fprintf(stderr, "Error %d on grantpt()\n", errno);
return 1;
}

rc = unlockpt(fdm);
if (rc != 0)
{
fprintf(stderr, "Error %d on unlockpt()\n", errno);
return 1;
}

// Open the slave side ot the PTY
fds = open(ptsname(fdm), O_RDWR);

// Create the child process
if (fork())
{
fd_set fd_in;

  // FATHER

  // Close the slave side of the PTY
  close(fds);

  while (1)
  {
    // Wait for data from standard input and master side of PTY
    FD_ZERO(&fd_in);
    FD_SET(0, &fd_in);
    FD_SET(fdm, &fd_in);

    rc = select(fdm + 1, &fd_in, NULL, NULL, NULL);
    switch(rc)
    {
      case -1 : fprintf(stderr, "Error %d on select()\n", errno);
                exit(1);

      default :
      {
        // If data on standard input
        if (FD_ISSET(0, &fd_in))
        {
          rc = read(0, input, sizeof(input));
          if (rc > 0)
          {
            // Send data on the master side of PTY
            write(fdm, input, rc);
          }
          else
          {
            if (rc < 0)
            {
              fprintf(stderr, "Error %d on read standard input\n", errno);
              exit(1);
            }
          }
        }

        // If data on master side of PTY
        if (FD_ISSET(fdm, &fd_in))
        {
          rc = read(fdm, input, sizeof(input));
          if (rc > 0)
          {
            // Send data on standard output
            write(1, input, rc);
          }
          else
          {
            if (rc < 0)
            {
              fprintf(stderr, "Error %d on read master PTY\n", errno);
              exit(1);
            }
          }
        }
      }
    } // End switch
  } // End while
}
else
{
struct termios slave_orig_term_settings; // Saved terminal settings
struct termios new_term_settings; // Current terminal settings

  // CHILD

  // Close the master side of the PTY
  close(fdm);

  // Save the defaults parameters of the slave side of the PTY
  rc = tcgetattr(fds, &slave_orig_term_settings);

  // Set RAW mode on slave side of PTY
  new_term_settings = slave_orig_term_settings;
  cfmakeraw (&new_term_settings);
  tcsetattr (fds, TCSANOW, &new_term_settings);

  // The slave side of the PTY becomes the standard input and outputs of the child process
  close(0); // Close standard input (current terminal)
  close(1); // Close standard output (current terminal)
  close(2); // Close standard error (current terminal)

  dup(fds); // PTY becomes standard input (0)
  dup(fds); // PTY becomes standard output (1)
  dup(fds); // PTY becomes standard error (2)

  // Now the original file descriptor is useless
  close(fds);

  // Make the current process a new session leader
  setsid();

  // As the child is a session leader, set the controlling terminal to be the slave side of the PTY
  // (Mandatory for programs like the shell to make them manage correctly their outputs)
  ioctl(0, TIOCSCTTY, 1);

  // Execution of the program
  {
  char **child_av;
  int i;

    // Build the command line
    child_av = (char **)malloc(ac * sizeof(char *));
    for (i = 1; i < ac; i ++)
    {
      child_av[i - 1] = strdup(av[i]);
    }
    child_av[i - 1] = NULL;
    rc = execvp(child_av[0], child_av);
  }

  // if Error...
  return 1;
}

return 0;
} // main

cfmakeraw() 应该在父进程而不是子进程中调用。然后所有用户输入(TAB、...)将由子进程处理。并且父端需要在退出前恢复原来的终端设置。

更新代码(1. 将 cfmakeraw() 从子级移动到父级;2. 恢复 exit() 之前的终端设置):

#define _GNU_SOURCE

#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <stdio.h>
#include <termios.h>
#include <sys/select.h>
#include <sys/ioctl.h>
#include <string.h>

struct termios save_termios;

void restore_term(void)
{
    tcsetattr(STDIN_FILENO, TCSANOW, &save_termios);
}

int main(int ac, char *av[])
{
    int fdm, fds;
    int rc;
    char input[150];

    tcgetattr(STDIN_FILENO, & save_termios);

    // Check arguments
    if (ac <= 1)
    {
        fprintf(stderr, "Usage: %s program_name [parameters]\n", av[0]);
        exit(1);
    }

    fdm = posix_openpt(O_RDWR);
    if (fdm < 0)
    {
        fprintf(stderr, "Error %d on posix_openpt()\n", errno);
        return 1;
    }

    rc = grantpt(fdm);
    if (rc != 0)
    {
        fprintf(stderr, "Error %d on grantpt()\n", errno);
        return 1;
    }

    rc = unlockpt(fdm);
    if (rc != 0)
    {
        fprintf(stderr, "Error %d on unlockpt()\n", errno);
        return 1;
    }

    // Open the slave side ot the PTY
    fds = open(ptsname(fdm), O_RDWR);

    // Create the child process
    if (fork())
    {
        fd_set fd_in;
        struct termios new_setting;

        // FATHER
        atexit(restore_term);

        new_setting = save_termios;
        cfmakeraw ( & new_setting);
        tcsetattr(STDIN_FILENO, TCSANOW, & new_setting);

        // Close the slave side of the PTY
        close(fds);

        while (1)
        {
            // Wait for data from standard input and master side of PTY
            FD_ZERO(&fd_in);
            FD_SET(0, &fd_in);
            FD_SET(fdm, &fd_in);

            rc = select(fdm + 1, &fd_in, NULL, NULL, NULL);
            switch(rc)
            {
            case -1 : fprintf(stderr, "Error %d on select()\n", errno);
                      exit(1);

            default :
                      {
                          // If data on standard input
                          if (FD_ISSET(0, &fd_in))
                          {
                              rc = read(0, input, sizeof(input));
                              if (rc > 0)
                              {
                                  // Send data on the master side of PTY
                                  write(fdm, input, rc);
                              }
                              else
                              {
                                  if (rc < 0)
                                  {
                                      fprintf(stderr, "Error %d on read standard input\n", errno);
                                      exit(1);
                                  }
                              }
                          }

                          // If data on master side of PTY
                          if (FD_ISSET(fdm, &fd_in))
                          {
                              rc = read(fdm, input, sizeof(input));
                              if (rc > 0)
                              {
                                  // Send data on standard output
                                  write(1, input, rc);
                              }
                              else
                              {
                                  if (rc < 0)
                                  {
                                      fprintf(stderr, "Error %d on read master PTY\n", errno);
                                      exit(1);
                                  }
                              }
                          }
                      }
            } // End switch
        } // End while
    }
    else
    {
        // CHILD

        // Close the master side of the PTY
        close(fdm);

        // The slave side of the PTY becomes the standard input and outputs of the child process
        close(0); // Close standard input (current terminal)
        close(1); // Close standard output (current terminal)
        close(2); // Close standard error (current terminal)

        dup(fds); // PTY becomes standard input (0)
        dup(fds); // PTY becomes standard output (1)
        dup(fds); // PTY becomes standard error (2)

        // Now the original file descriptor is useless
        close(fds);

        // Make the current process a new session leader
        setsid();

        // As the child is a session leader, set the controlling terminal to be the slave side of the PTY
        // (Mandatory for programs like the shell to make them manage correctly their outputs)
        ioctl(0, TIOCSCTTY, 1);

        // Execution of the program
        {
            char **child_av;
            int i;

            // Build the command line
            child_av = (char **)malloc(ac * sizeof(char *));
            for (i = 1; i < ac; i ++)
            {
                child_av[i - 1] = strdup(av[i]);
            }
            child_av[i - 1] = NULL;
            rc = execvp(child_av[0], child_av);
        }

        // if Error...
        return 1;
    }

    return 0;
} // main