【问题标题】:Keys like TAB and KEY_UP don't work as expected (e.g. KEY_UP becomes "^[[A") in PTY program像 TAB 和 KEY_UP 这样的键不能按预期工作(例如 KEY_UP 在 PTY 程序中变成“^[[A”)
【发布时间】:2018-04-08 11:09:40
【问题描述】:

我有一个 pty 代码示例,(可能是最流行的 pty 示例)我正在尝试使用它在从属设备中启动 sh [shell 终端],但是 cd 或 key up [last command] 等热键不起作用 [什么也没做] 我用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下:

我们可以使 mypty2 更通用,以便能够在 pty(从端)后面执行任何程序。在 mypty3 中,父进程将其标准输入中的所有数据写入 pty 的主控端,并将 pty 主控端的所有数据写入其标准输出。子进程的行为与 mypty2 中的相同,但执行一个交互式程序,并将其参数作为参数传递给程序。我们可以注意到对setsid()ioctl(TIOCSCTTY) 的调用,使pty 成为执行程序的控制终端。我们还可以注意到 fds 文件描述符的关闭,它在调用 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

【问题讨论】:

标签: c sh pty


【解决方案1】:

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

【讨论】:

  • 我已尝试在孩子和父亲中进行此操作://打开 PTY fds = open(ptsname(fdm), O_RDWR); // 创建子进程 if (fork()) { fd_set fd_in; // 父亲结构 termios slave_orig_term_settings; // 保存的终端设置 tcgetattr(fds, &slave_orig_term_settings); cfmakeraw (&slave_orig_term_settings);它不起作用,要如何将发送到 cfmakeraw 的 termios 结构进行 intizlize?
  • 在答案中粘贴代码。在 debian9 上测试过,对我来说效果很好。试一试。
  • 这段代码也适用于我 [在 Ubnutu 16.4 上],非常感谢
猜你喜欢
  • 1970-01-01
  • 2021-05-12
  • 1970-01-01
  • 2014-09-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-05-16
相关资源
最近更新 更多