[comp.windows.x] I'm looking for code to invoke a slave xterm

yost@esquire.UUCP (David A. Yost) (08/20/90)

I'd like to fork and exec an xterm that has no shell
running in it and whose stdin and stdout are hooked up
to two file descriptors in my process.  Anyone done this?

 --dave yost
   yost@dpw.com or uunet!esquire!yost
   Please ignore the From or Reply-To fields above, if different.

richard@aiai.ed.ac.uk (Richard Tobin) (08/21/90)

In article <2247@esquire.UUCP> yost@esquire.UUCP (David A. Yost) writes:
>I'd like to fork and exec an xterm that has no shell
>running in it and whose stdin and stdout are hooked up
>to two file descriptors in my process.  Anyone done this?

Yes, but you have to do it through a pseudo-terminal.

Here's a program that illustrates it.

-- Richard

/* slavewindow.c
 *
 * Copyright Richard Tobin / AIAI 1990.
 * May be freely distributed if this notice remains intact.
 *
 * Creates a window (xterm) and accepts commands to display text,
 * move cursor, etc.  Also returns keys typed in the window.

 * Commands are:
 * 
 * clear	clear the screen
 * move r c	move to row r, col c
 * string ...	display the string (the rest of the line)
 * char c	display the character with ascii code c
 * format ...	set the format for returning characters typed
 * raw		set raw mode (characters returned immediately)
 * cooked	set cooked mode (characters not returned until return typed)
 * echo		set echo mode
 * noecho	set noecho mode (useful with raw)
 * 
 * The arguments to string and format may contain C style escapes (\n etc).
 *
 * The default format for returning characters is 
 *    char(%d).\n
 * In cooked mode, typing an end-of-file causes -1 to be returned.
 * 
 * Closing the pipe to the process will cause the window to exit.
 *    
 */

#include <errno.h>
#include <stdio.h>
#include <fcntl.h>
#include <string.h>
#include <sys/ioctl.h>

extern int errno;
void process_command(), process_input(), return_char(), copy_term(), escape();
int create_window();

int win;
char format[80] = "char(%d).\n";

main()
{
    win = create_window();

    while(1)
    {
	int stat, bits;
	char buf[500];

	bits = ((1 << 0) | (1 << win));
	stat = select(win+1, &bits, (int *)0, (int *)0, (struct timeval *)0);

	if(stat == -1 && errno == EINTR)
	    continue;

	if(stat == -1)
	{
	    perror("slavewindow: select");
	    exit(1);
	}

	if(bits & (1 << win))
	{
	    switch(read(win, buf, 1))
	    {
	      case 0:		/* eof typed */
		return_char(-1);
		break;

	      case 1:
		return_char(((int)buf[0] & 255));
		break;

	      default:
		if(errno == EINTR)
		    break;
		perror("slavewindow: window read");
		exit(1);
	    }
	}

	if(bits & (1 << 0))
	{
	    stat = read(0, buf, sizeof(buf));

	    if(stat == 0)	/* eof means exit */
		exit(0);

	    if(stat == -1)
	    {
		if(errno == EINTR)
		    continue;
		perror("slavewindow: pipe read");
		exit(1);
	    }

	    process_input(buf, stat);
	}
    }
}

void process_input(data, dlen)
char *data;
int dlen;
{
    static char buf[500];
    static int len = 0;

    while(dlen > 0 && len < sizeof(buf)-1)
    {
	buf[len++] = *data++;
	dlen--;

	if(buf[len-1] == '\n')
	{
	    buf[len] = '\0';
	    process_command(buf);
	    len = 0;
	}
    }
}

#define Prefix(pre, str) (strncmp(pre, str, sizeof(pre)-1) == 0)

#define CLEAR "\033[H\033[2J"
#define MOVE "\033[%d;%dH"

void process_command(command)
char *command;
{
    int row, col, c;
    char buf[100], *s;
    struct sgttyb sgtty;

    if(Prefix("clear", command))
	write(win, CLEAR, sizeof(CLEAR)-1);

    else if(Prefix("move", command))
    {
	sscanf(command, "move %d %d", &row, &col);
	sprintf(buf, MOVE, row+1, col+1);
	write(win, buf, strlen(buf));
    }

    else if(Prefix("string ", command))
    {
	s = command+7;
	escape(s);
	write(win, s, strlen(s)-1);
    }
    
    else if(Prefix("char", command))
    {
	sscanf(command, "char %d", &c);
	buf[0] = c;
	write(win, buf, 1);
    }

    else if(Prefix("raw", command))
    {
	ioctl(win, TIOCGETP, &sgtty);
	sgtty.sg_flags |= RAW;
	ioctl(win, TIOCSETP, &sgtty);
    }
    
    else if(Prefix("cooked", command))
    {
	ioctl(win, TIOCGETP, &sgtty);
	sgtty.sg_flags &= ~RAW;
	ioctl(win, TIOCSETP, &sgtty);
    }
    
    else if(Prefix("echo", command))
    {
	ioctl(win, TIOCGETP, &sgtty);
	sgtty.sg_flags |= ECHO;
	ioctl(win, TIOCSETP, &sgtty);
    }
    
    else if(Prefix("noecho", command))
    {
	ioctl(win, TIOCGETP, &sgtty);
	sgtty.sg_flags &= ~ECHO;
	ioctl(win, TIOCSETP, &sgtty);
    }
    
    else if(Prefix("format ", command))
    {
	s = command+7;
	escape(s);
	strncpy(format, s, sizeof(format));
	format[strlen(format)-1] = '\0';   /* get rid of the linefeed */
    }

    else
	fprintf(stderr, "slavewindow: unknown command %s\n", command);
}

void escape(s)			/* interpret \n etc */
char *s;
{
    char *p = s;
    int escaped = 0;

    while(*s)
    {
	if(escaped)
	{
	    char *a = "aenrt\\", *b = "\007\033\n\r\t\\";
	    char *i = strchr(a, *s);

	    *p++ = i ? b[i-a] : *s;

	    escaped = 0;
	}
	else
	{
	    if(*s == '\\')
		escaped = 1;
	    else
		*p++ = *s;
	}

	s++;
    }

    *p = '\0';
}
		
void return_char(c)
{
    char buf[100];

    sprintf(buf, format, c);

    write(1, buf, strlen(buf));
}


#define MASTER  "/dev/ptyXY"
#define SLAVE   "/dev/ttyXY"

int create_window()
{
    static char master_path[] = MASTER, slave_path[] = SLAVE, buf[100];
    int i, master, slave, oldtty;

    strcpy(master_path, MASTER);
    strcpy(slave_path,  SLAVE );

    for(i=0; i <= ('s'-'p') * 16; ++i)
    {
        master_path[strlen(MASTER)-2] = i / 16 + 'p';
        master_path[strlen(MASTER)-1] = "0123456789abcdef"[i & 15];
        master = open(master_path,O_RDWR);
        if(master >= 0)
	    break;
    }

    if(master < 0)
    {
	fprintf(stderr, "slavewindow: can't get a pty\n");
	exit(1);
    }

    slave_path[strlen(SLAVE)-2] = master_path[strlen(MASTER)-2];
    slave_path[strlen(SLAVE)-1] = master_path[strlen(MASTER)-1];

    sprintf(buf, "-S%s%d", &slave_path[strlen(slave_path)-2], master);

    if(vfork() == 0)
    {
	setpgrp(0, getpid());

	execlp("xterm", "xterm", buf, 0);
	fprintf(stderr, "xterm exec failed\n");
	_exit(1);
    }

    close(master);

    slave = open(slave_path, O_RDWR, 0);

    read(slave, buf, sizeof(buf)); /* suck xterm junk before echo on */

    oldtty = open("/dev/tty", O_RDWR, 0);
    copy_term(oldtty, slave);
    close(oldtty);

    return slave;
}

void copy_term(from, to)
int from, to;
{
    int err;

    struct sgttyb sb;
    struct tchars tc;
    struct ltchars ltc;
    int bits;

    err  = ioctl(from, TIOCGETP, &sb);
    err |= ioctl(to, TIOCSETP, &sb);
    err |= ioctl(from, TIOCGETC, &tc);
    err |= ioctl(to, TIOCSETC, &tc);
    err |= ioctl(from, TIOCLGET, &bits);
    err |= ioctl(to, TIOCLSET, &bits);
    err |= ioctl(from, TIOCGLTC, &ltc);
    err |= ioctl(to, TIOCSLTC, &ltc);

    if(err != 0)
	perror("can't copy terminal modes");
}
-- 
Richard Tobin,                       JANET: R.Tobin@uk.ac.ed             
AI Applications Institute,           ARPA:  R.Tobin%uk.ac.ed@nsfnet-relay.ac.uk
Edinburgh University.                UUCP:  ...!ukc!ed.ac.uk!R.Tobin

yost@DPW.COM (David A. Yost) (09/27/90)

In article <2247@esquire.UUCP> I wrote:
>I'd like to fork and exec an xterm that has no shell
>running in it and whose stdin and stdout are hooked up
>to two file descriptors in my process.  Anyone done this?

Here's a way to do it:

   xterm -e sockio -il xtermsocket &

This runs sockio in the xterm window instead of a
shell.  The -i option tells sockio to run in interactive
(bidirectional copy) mode, and the l option tells it to
listen and accept a connection on "xtermsocket", which
is a unix domain socket.  Then to talk to the xterm
window, you do this:

   sockio -i xtermsocket

I will post sockio to comp.unix.sources sometime soon.
Here's what it does:

Usage: sockio <options> [ -l [ -c ] ] <name>
or:    sockio <options> -d inet <socket> [ <host> ]
or:    sockio <options> -d inet -l [ -c ] [ <socket> ]

Options: [ -k ] [ -i ] or [ -r [ -a ] ] [ -v ] [ -s <size> ]

Copies between unix or inet stream socket
and standard input and/or output in one of three modes:
By default, copies from stdin to socket (write mode).
-r Read mode: copies from socket to stdout.
-i Interactive mode: copies from socket to stdout and from stdin to socket.
-l Sockio listens on the socket and accepts a connection.  If unix
   domain, removes the socket file when it exits or is killed.
-c Sockio continually listens for and accepts new connections.
   Otherwise, when the other end closes the connection, sockio exits.
-k When the -l option is not given, by default sockio expects the socket
   to already exist, and exits with status 1 if it doesn't exist.
   If the -k option is given, it keeps trying to connect.
-s <size>
   How many bytes or lines to copy.  A numeric size is a number of bytes,
   a number followed by an 'l' is a number of lines.
-a Used only with -r, the -a option tells sockio to read all it can
   (up to the given size, if any) until the socket becomes empty, then exit.
-v Verbosity: notify on connects and accepts.
-d inet
   Additional arguments:
   A <host> name or numeric address in dot notation, default is this host.
   A <socket> argument which can be a number or a service name.
	In the -l case, <socket> can be unspecified, in which case a socket
	number is assigned by the system, and the number reported to stderr.

If no -d option is given, then the <name> given is taken
as a filename representing a unix domain socket.

Examples:
   sockio -rlc logs | tee logfile &
   date | sockio logs
   date | sockio logs
or:
   awk < /dev/null 'BEGIN { for (;;) print ++n }' | sockio -lc -s 1l numbers &
   sockio -r numbers
   sockio -r numbers
or:
   xterm -e sockio -il xtermsocket &
   sockio -i xtermsocket

Bug: if the writer is invoked with -l and -c, output data stranded in the
socket when the reader closes the socket is lost.

 --dave yost
   yost@dpw.com or uunet!esquire!yost
   Please don't use other mangled forms you may see
   in the From, Reply-To, or CC fields above.