0

I want to know how to create a fully interactive terminal. I am creating an interactive bash terminal like this:

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. My problem is how do I automate CTRL & SHIFT keys? Python's subprocess does this so it's definitely possible.

  1. How to send ctrl-c to kill foreground process? In python it would be the follow and I want to know what it looks like in C:
process = subprocess.Popen(..) ...
process.send_signal(signal.SIGINT)
  1. How to send shift, ctrl, esc keys? For example, how to open nano, then send ctrl-a-esc? In python it would be the following & I want to know what it looks like in 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)`
dbush
  • 205,898
  • 23
  • 218
  • 273
u7uuuuu
  • 3
  • 1
  • Please consider posting these two questions separately. #1 is a duplicate of [How do I send a signal to a process in C?](https://stackoverflow.com/questions/7696925/how-to-send-a-signal-to-a-process-in-c), but since the post contains a second unrelated question I'm unable to deduplicate it – that other guy Mar 01 '19 at 19:52
  • @the other guy - No1 isn't a duplicate, I've already seen that and the codes are different - I'm using an interactive terminal that is called via execvp, not just a tsraight fork() - but if it's a duplicate feel free to show the solution. The answers to 1 & 2 should be the same thing... – u7uuuuu Mar 01 '19 at 20:06
  • I'm not sure I understand. You *do* have a `fork()` there. Why can't get its return value with `int pid = fork();` and then `kill(pid, SIGINT);` from the parent? That's exactly what your Python code does – that other guy Mar 01 '19 at 20:50
  • @the other guy - Indeed you don't understand. kill(pid) would kill the entire /bin/sh -i process, not the foreground process of the psuedo tty.... – u7uuuuu Mar 01 '19 at 22:07
  • Oh, I see. I was confused because your Python code would also kill the process regardless of any PTYs, and you asked for a C equivalent. If the Python code doesn't do what you want to do, I suggest you remove it to avoid confusion or at least describe what your C code should do differently from it – that other guy Mar 01 '19 at 22:26
  • @the other guy - Wrong again, I suggest you avoid talking about things you don't know about... it's obvious you don't know the answer here. – u7uuuuu Mar 01 '19 at 22:29
  • It's a good suggestion in general, but in this case Linux agrees with me: I put the Python code in a file, ran it with `strace`, and confirmed that the underlying syscalls are a `fork` (specifically `clone`) followed by an `execve` in the child process and `kill(pid, SIGINT)` in the parent process. You should also be aware that PTYs (and TTYs) do not have a concept of pressing and releasing modifier keys, and that Shift+A simply sends an uppercase A and no Shift status. – that other guy Mar 01 '19 at 22:37
  • @the other guy, lol... "linux agrees with me".. are you even a C programmer? So if you're correct, then take the source and prove it (rachid.koucha.free.fr/tech_corner/pty_pdip.html), other wise stop wasting my time, because that's all you're doing here, your input on this topic is useless, and I already told you that kills the whole process not just the foreground running. So post a fully working sample code, or GTFO. – u7uuuuu Mar 01 '19 at 23:11
  • I already mentioned how you can verify for yourself that the Python code doesn't match your description. Unfortunately you didn't simply double-check this and update the question to be more clear. I did post an example though, along with an extended description of why the Python code doesn't do what you want. If you find any issues with it, I hope you'll choose a better way to express it. – that other guy Mar 02 '19 at 00:34

2 Answers2

0

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
    • exec the shell
  • in the parent, interact with the shell via the master 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.

Chris Dodd
  • 119,907
  • 13
  • 134
  • 226
  • As I already mentioned in my OP - I can already do everything you said and my question is involved **specifically** for shift/ctrl/etc keys, not anything you described. This isn't a "vague description" and I've included every important API call but if you don't already know the answer, a source code isn't going to help you. Here's the full source though - http://rachid.koucha.free.fr/tech_corner/pty_pdip.html . Perhaps you know another forum of _professionals_ that'd be able to answer such questions? I'm getting impression this is mostly hobbyists here. – u7uuuuu Mar 01 '19 at 22:14
  • Shift and ctrl are modifiers -- you create them by writing upper case or control characters to the master pty. ESC is just character 0x1b, so you can send that directly. ctrl-A-esc would be the sequence 0x01 0x1b – Chris Dodd Mar 01 '19 at 23:46
0

There are four separate issues here:

1. How to trigger a Ctrl-C on the PTY

The sigint is triggered by the terminal's line discipline upon receiving Ctrl-C as input (in cooked mode). You can do this by sending 0x03, which is equivalent to uppercase ASCII 'C' with the 7th bit cleared:

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

2. The C equivalent of 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. You can see this in 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

Esc is simple: you just send its ASCII value. Here's man ascii:

033   27    1B    ESC (escape) 

Shift and Control are modifiers, so you don't actually send them. You just modify the character you send. #1 already covered how to do this for Ctrl on simple ascii characters.

For shift, you should simply uppercase the character you're interested in using appropriate string functions. 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.

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.

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.


4. The C equivalent of the piping to xte

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. 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. It is definitely nothing like #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.


You can find a complete example for sending Ctrl+C adapted from your code here:

#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:

$ ./foo
From PTY:
sleep 60
$ ^C
echo 'Shell is still alive'
$ Shell is still alive
$
Community
  • 1
  • 1
that other guy
  • 116,971
  • 11
  • 170
  • 194
  • Yes this sample code works. Thanks. Please excuse my tone in the other thread, as I'm sure you know lots of wannabes on this site trying to sound smart. Not you though ; ) – u7uuuuu Mar 02 '19 at 01:08