简体   繁体   English

如何在C中使用shift + Ctrl键获得完全交互式的bash终端?

[英]How to get fully interactive bash terminal in C with shift + ctrl keys?

I want to know how to create a fully interactive terminal. 我想知道如何创建一个完全交互式的终端。 I am creating an interactive bash terminal like this: 我正在创建一个交互式bash终端,如下所示:

fds = open(ptsname(fdm), O_RDWR);

if (fork())
{
     ....
}
else
{
    ...

    ioctl(0, TIOCSCTTY, 1);

    // Execution of the program
    {

            char *child_av[] = {"/bin/sh", "-i", NULL};


            rc = execvp(child_av[0], child_av);
    }

    // if Error...
    return -1;
}

I can then go on to use read()/write() to send commands and receive output. 然后,我可以继续使用read()/ write()发送命令并接收输出。 My problem is how do I automate CTRL & SHIFT keys? 我的问题是如何自动执行CTRL和SHIFT键? Python's subprocess does this so it's definitely possible. Python的子进程可以做到这一点,因此绝对有可能。

  1. How to send ctrl-c to kill foreground process? 如何发送ctrl-c杀死前台进程? In python it would be the follow and I want to know what it looks like in C: 在python中将是以下内容,我想知道它在C语言中的样子:
process = subprocess.Popen(..) ...
process.send_signal(signal.SIGINT)
  1. How to send shift, ctrl, esc keys? 如何发送班次,Ctrl,Esc键? For example, how to open nano, then send ctrl-a-esc? 例如,如何打开nano,然后发送ctrl-a-esc? In python it would be the following & I want to know what it looks like in C: 在python中将是以下内容,我想知道它在C中是什么样的:
from subprocess import Popen, PIPE

shift_a_sequence = '''keydown Shift_L
key A
keyup Shift_L
'''

def keypress(sequence):
    p = Popen(['xte'], stdin=PIPE)
    p.communicate(input=sequence)`

You seem to have most of the framework needed: 您似乎拥有所需的大多数框架:

  • create a pseuso-terminal 创建一个假终端
  • fork 叉子
    • in the child, dup2 the slave pty to stdin/stdout/stderr 在孩子中,将奴隶pty dup2设置为stdin / stdout / stderr
    • exec the shell 执行外壳
  • in the parent, interact with the shell via the master pty 在父级中,通过主pty与外壳交互

For more detail on where you are going wrong, you'd need to provide a Minimal, Complete, and Verifiable example of what you are actually trying to do, not just a vague description. 有关错误原因的更多详细信息,您需要提供一个最小,完整和可验证的示例来说明您实际要做什么,而不仅仅是模糊的描述。

There are four separate issues here: 这里有四个独立的问题:

1. How to trigger a Ctrl-C on the PTY 1.如何在PTY上触发Ctrl-C

The sigint is triggered by the terminal's line discipline upon receiving Ctrl-C as input (in cooked mode). 当接收到Ctrl-C作为输入时(在烹饪模式下),终端的线路规程会触发sigint。 You can do this by sending 0x03, which is equivalent to uppercase ASCII 'C' with the 7th bit cleared: 您可以通过发送0x03来做到这一点,它等于清除了第7位的大写ASCII'C':

write(fdm, "\x03", 1);

2. The C equivalent of process.send_signal 2. C等效于process.send_signal

 process = subprocess.Popen(..) ... process.send_signal(signal.SIGINT) 

This simply does a fork + kill , so it's unrelated to anything you'd do to accomplish #1. 这只是执行fork + kill ,因此它与您为实现#1所做的一切无关。 You can see this in strace : 您可以在strace看到:

$ cat foo.py
import signal
import subprocess
process = subprocess.Popen(["sleep", "10"])
process.send_signal(signal.SIGINT)

$ strace -f -eclone,kill,execve  python foo.py
execve("/usr/bin/python", ["python", "foo.py"], 0x7ffe4d179458 /* 30 vars */) = 0
clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7fa0f6e14a10) = 12917
strace: Process 12917 attached
[pid 12917] execve("/usr/bin/sleep", ["sleep", "10"], 0x7ffe6a45efc0 /* 30 vars */) = -1 ENOENT (No such file or directory)
[pid 12917] execve("/bin/sleep", ["sleep", "10"], 0x7ffe6a45efc0 /* 30 vars */ <unfinished ...>
[pid 12916] kill(12917, SIGINT)         = 0
[pid 12917] <... execve resumed> )      = 0
[pid 12917] --- SIGINT {si_signo=SIGINT, si_code=SI_USER, si_pid=12916, si_uid=1000} ---
[pid 12917] +++ killed by SIGINT +++

3. How to send Shift, Ctrl and Esc 3.如何发送Shift,Ctrl和Esc

Esc is simple: you just send its ASCII value. Esc很简单:您只需发送其ASCII值即可。 Here's man ascii : 这是man ascii

033   27    1B    ESC (escape) 

Shift and Control are modifiers, so you don't actually send them. Shift和Control是修饰符,因此您实际上并不发送它们。 You just modify the character you send. 您只需修改发送的字符。 #1 already covered how to do this for Ctrl on simple ascii characters. #1已经介绍了如何在简单的ascii字符上使用Ctrl进行此操作。

For shift, you should simply uppercase the character you're interested in using appropriate string functions. 对于shift,您应该使用适当的字符串函数将您感兴趣的字符简单地大写。 The traditional hardware logic of clearing/setting bit 6 doesn't work well for unicode characters, but 'c' == 'C'+0x20 if you feel like it. 清除/设置位6的传统硬件逻辑不适用于unicode字符,但是如果您愿意,可以使用'c' == 'C'+0x20

Note that this does not apply to things like arrow keys, where you need to send a different ANSI escape code corresponding to the terminal you're trying to emulate. 请注意,这不适用于箭头键之类的东西,在该情况下,您需要发送与您要模拟的终端相对应的不同ANSI转义码。

For completeness, Alt/Meta has two forms: Traditionally setting bit 8 (mostly deprecated for Unicode reasons), or Meta-As-Escape where you simply send the two bytes ESC x for Alt+x . 为了完整起见,Alt / Meta具有两种形式:传统上将bit 8设置(大多出于Unicode原因而弃用),或Meta-As-Escape,您只需将两个字节的ESC x发送给Alt+x


4. The C equivalent of the piping to xte 4.等效于xte的管道的C

 from subprocess import Popen, PIPE shift_a_sequence = '''keydown Shift_L key A keyup Shift_L ''' def keypress(sequence): p = Popen(['xte'], stdin=PIPE) p.communicate(input=sequence)` 

This opens an X11 utility that simulates an X11 key sequence. 这将打开一个模拟X11按键序列的X11实用程序。 If you are running the program in an XTerm and don't switch focus, this will hopefully end up in the terminal where you started, but this is not at all a guarantee. 如果您在XTerm中运行该程序并且不切换焦点,则希望最终会在您启动的终端中结束,但这完全不能保证。 It is definitely nothing like #3. 绝对不像#3。

If you wanted to do this though, you can use popen from C in much the same way, but it won't help you do anything of what you describe textually. 但是,如果要执行此操作,则可以使用与C popen的方式来使用popen ,但它不会帮助您执行任何用文本描述的内容。


You can find a complete example for sending Ctrl+C adapted from your code here: 您可以在此处找到根据代码发送改编的Ctrl+C的完整示例:

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

#define guard(x) do { if((x)<0) { perror(#x); exit(1); } } while(0);
int main(void)
{
int fdm, fds, rc;
char input[150];
fdm = posix_openpt(O_RDWR);
guard(grantpt(fdm));
guard(unlockpt(fdm));

if (fork())
{
  char* output = "sleep 60\n";
  // Start a long sleep
  write(fdm, output, strlen(output));
  // Wait and send Ctrl-C to abort it after 1 second
  sleep(1); write(fdm, "\x03", 1);
  // Make sure shell is still alive
  output = "echo 'Shell is still alive'\n";
  write(fdm, output, strlen(output));
  // Wait and send Ctrl-D to exit
  sleep(1); write(fdm, "\x04", 1);

  while((rc = read(fdm, input, sizeof(input)-1)) > 0) {
    input[rc] = 0;
    printf("From PTY:\n%s\n", input);
  }
  close(fdm);
  wait(NULL);
}
else
{
  setsid();
  guard(fds = open(ptsname(fdm), O_RDWR));
  close(fdm);
  dup2(fds, 0);
  dup2(fds, 1);
  dup2(fds, 2);
  close(fds);
  execlp("sh", "sh", "-i", NULL);
}

return 0;
} // main

Execution shows that sleep is interrupted, the shell continues, and is finally exited: 执行表明睡眠被中断,shell继续运行,最后退出:

$ ./foo
From PTY:
sleep 60
$ ^C
echo 'Shell is still alive'
$ Shell is still alive
$

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM