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.
- 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)
- 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)`
Advertisement
Answer
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 60n"; // 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%sn", 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 $