[comp.unix.questions] BSD 4.2 TALK FACILITY

maw@auc (Michael A. Walker) (12/06/86)

Greetings:

	My name is Michael A. Walker and I am an operator at the Atlanta
University Center Computer Center in Atlanta, GA.  We are a fairly new UNIX 
shop, running UNIX SYSTEM V and we are in the process of building our software 
and hardware inventory.

	We are aware that there exists a TALK facility for the BSD 4.2 systems
and that it has been distributed to educational institutions such as ours at no
charge.  This facility will greatly enhance the intercommunication links with 
our users and staff.  Presently, we can only WRITE to each other.  This method
is very tedious to use due to the existence of a long lag time between persons 
receiving the incoming messages.

	I am requesting from anyone to either send me the sources for this
facility or send me information(i.e. who I can contact directly to request a
copy of TALK).  All help will be greatly appreciated.

	With kindest regards, I remain

                                                 Sincerely,

						 Michael A. Walker
						 Operator, AUC Computer Center
						 maw@auc

rab@smu.UUCP (12/08/86)

this was posted some time ago...

we are using it on a 3b2 sys V.2.1 and have no problems.

enjoy.


#!	/bin/sh
#
#	This is a shell archive, meaning:
#
#	1. Remove everything above the #! /bin/sh line.
#	2. Save the resulting text in a file.
#	3. Execute the file with /bin/sh (not csh) to create:
#			README
#			Makefile
#			talk.mansrc
#			talk.c
#
#
# This archive created: Tue Jun  3 10:13:56 BST 1986
echo shar: "extracting 'README'" '(266 characters)'
cat << \SHAR_EOF > 'README'

	This is a talk utility for System V.  To make it simply
type in 'make' or 'make talk'.  In order to format the manual
page type in 'make man'.  I'll admit the manual page is not exactly
up to scratch - I just wanted to get the program out there for people
to use.
SHAR_EOF
echo shar: "extracting 'Makefile'" '(124 characters)'
cat << \SHAR_EOF > 'Makefile'
#	Makefile for Sys V talk
#
talk:	talk.c
	cc -O -o talk talk.c -lcurses
man:	talk.mansrc
	nroff -man talk.mansrc > talk.man
SHAR_EOF
echo shar: "extracting 'talk.mansrc'" '(2400 characters)'
cat << \SHAR_EOF > 'talk.mansrc'
.TH Talk 1
.SH NAME
talk \- talk to another user
.SH SYNOPSIS
.B talk user [ tty ]
.SH DESCRIPTION
.PP
Talk is a utility that enables two users to interactively
communicate on a character basis on split screens.
It is intended to supersede the write utility for interactive
use by providing a more useful service.

Talk is invoked with a user name and an optional tty name thus :-

        talk user [ tty8 ]

.PP
If user happens to be logged in more than once and no tty name is
supplied, talk will use the first entry in /etc/utmp (as used by who).
.PP
Talk will then attempt to notify the requested user that you are
trying to talk with him.
Trying to talk to a user may fail for two reasons :-

        The requested user is not logged in.

        The requested user has disabled messages (via mesg)

.PP
If the user doesn't answer, talk will keep notifying him.
After a reasonable number of retries, talk will give up and exit.
.PP
To reply to a user trying to talk to you, you should type
talk user (as shown in the request message).
.PP
When your party has connected you may both begin to talk (this
will be indicated by the bell ringing on your terminal).
The name of the person you are talking to and an elapsed time
indicator will appear in the centre of the screen.
You will notice that there are bursts of input and
output - this is due to the way they are handled within talk.
In order to avoid busy waiting, a sleep of one
second occurs whenever there is no activity.
.bp
.PP
Certain characters when typed have special meaning :-


ctrl-l    -    Refresh the screen.  Useful if the
               screen gets corrupted.

ctrl-d    -    Disconnect - finish talking.

ctrl-g    -    Ring the bell on other users terminal.

delete    -    Delete the character before the cursor
               (works backwards over lines as well).
               Uses your normal delete key.

ctrl-c    -    Forced exit.  This has the same effect
               as disconnect, although it may be
               used at any stage of the proceedings
               (ie. before connection has occured).


.SH FILES
/etc/utmp               to find recipient's tty
/tmp/tr_xxxxxxxx        temporary files (named pipes) for
/tmp/tw_xxxxxxxx        communication pickup
.SH BUGS
.PP
One second delay between bursts of activity (to be fixed when using
version 8).
.PP
Restricted to the domain of current machine.
SHAR_EOF
echo shar: "extracting 'talk.c'" '(14375 characters)'
cat << \SHAR_EOF > 'talk.c'
#ifndef	lint
static char sccsid[] = "@(#)talk.c  1.2 [Nigel Holder (C) -  Baddow] 04/12/85";
#endif


/***************************************
*
*	Author   :  Nigel Holder
*
*	Date     :  12 June 1985
*		     4 December 1985		changed elapsed time stuff
*						to be synchronous to windows
*
*
*	Copyright (C) 1986  by Nigel Holder
*
*	   Permission to use this program is granted, provided it is not
*	sold, or distributed for direct commercial advantage, and includes
*	the copyright notice and this clause.
*
*
*	   Talk - an interactive communication program that allows users
*	to talk on a character basis (as opposed to a line basis, as for
*	the system write utility).
*
*	   Written for System V as it uses named pipes !
*	   (and BSD already has a talk utility).
*
*	Bugs:
*
*	1.   Not as good as BSD talk, but it suffices.
*	    (restricted to current host machine)
*
*	2.   Really need select() type statement (BSD) instead of sleeping
*	     for 1 second between no input or output activity.
*	     (version 8 should fix this).
*
*	3.   Should check for name fields in dividewin overflowing screenwidth
*
*	4.   Probably should disable CTRL-c stopping program when connected.
*
***************************************/


#include <curses.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <ctype.h>
#include <pwd.h>
#include <utmp.h>
#include <sys/dir.h>


#define		FIFO			( 0010660 )
#define		EXIST			( 00 )
#define         INWINLINES              ( LINES / 2 )
#define         OUTWINLINES             ( LINES - INWINLINES - 1 )
#define		CONNECT			( 0xF0 )
#define		DISCONNECT		( 0xF3 )
#define		DELETE			( 0xFC )
#define		END_OF_FILE		( 0x04 )
#define		REFRESH			( 0x0C )
#define		SPACE			( 0x20 )
#define		BELL			( 0x07 )
#define		CLOCK_TEMP		( sizeof(elapsed) )
#define		CLOCK_TICK		( 15 )
#define		RETRIES			( 3 )
#define		TRIES			( RETRIES + 1 )
#define		WAIT_TIME		( 20 )
#define		DIRPREFIX		( sizeof(tempdir) + 20 )

#define		not_printable(x)	\
				(x != '\n'  &&  x != '\t'  &&  isprint(x) == 0) 

/*  --  globals  --  */

FILE	*writing = NULL;			/*  pipe to write down  */
int	reading = -1;				/*  fd for keyboard  */
int	connected = FALSE;			/*  whether actually talking  */
int	failed = FALSE;				/*  couldn't connect  */
int	forced = FALSE;				/*  ctrl-c stopped program  */
int	time_changed = FALSE;			/*  to update elapsed time  */

WINDOW	*inwin, *outwin, *dividewin;		/*  the three windows  */
int	inwinx, inwiny;				/*  cursor pos in each window */
int	outwinx, outwiny;
int	divwiny, divwinx;

char	elapsed[] = "[%02d:%02d:%02d]  ";	/*  format of elapsed time  */
char	tempdir[] = "/tmp/";			/*  temp place for pipes  */
char	wprefix[] = "tw_";			/*  prefix for pipes  */
char	rprefix[] = "tr_";
char	writefile[DIRSIZ + DIRPREFIX];		/*  temp pipe filenames  */
char	readfile[DIRSIZ + DIRPREFIX];
char	tmp[BUFSIZ];				/*  general purpose !  */
char	*myname, *theirname, *theirtty;
char	*progname;

int	master;					/*  whether master or slave   */
int	delchar;				/*  favourite delete char  */




main(argc, argv)

int	argc;
char	*argv[];

{
	int	forced_die();
	char	*strrchr(), *getlogin();

	if ((progname = strrchr(argv[0], '/')) != NULL)   {
		++progname;
	}
	else   {
		progname = argv[0];
	}
	if (argc < 2)   {
		fprintf(stderr, "usage: %s user [tty]\n", progname);
		exit(1);
	}
	theirname = argv[1];
	if (argc > 2)   {
		theirtty = argv[2];
	}
	else   {
		theirtty = "";
	}
	if ((myname = getlogin()) == NULL)   {
		fprintf(stderr, "You don't exist. Go away.\n");
		exit(2);
	}
	signal(SIGINT, SIG_IGN);		/*  play safe  */
	signal(SIGHUP, SIG_IGN);
	signal(SIGQUIT, SIG_IGN);

	screen_init();				/*  set up windows  */
	signal(SIGINT, forced_die);		/*  gracefully trap signals  */
	signal(SIGHUP, forced_die);
	set_up_channel();			/*  establish named pipes  */
	talk();			/*  let your fingers do the walking !  */
	die();					/*  should never get here !  */
	exit(3);
}



set_up_channel()		/*  establish connection between users  */

{
	char	*nameof();
	char	clock_temp[CLOCK_TEMP];

	sprintf(readfile, "%s%s%s%s", tempdir, rprefix, myname, theirname);
	sprintf(writefile, "%s%s%s%s", tempdir, wprefix, myname, theirname);

	/*  check to see if any old connections lying around !  */
	/*  they could be caused (left around) by system crashes etc.  */
	if (( ! access(readfile, EXIST))  &&  out_of_date(readfile))   {
		unlink(readfile);
	}
	if (( ! access(writefile, EXIST))  &&  out_of_date(writefile))   {
		unlink(writefile);
	}
	wheader(inwin, "[No connection yet]\n");
	sprintf(readfile, "%s%s%s%s", tempdir, wprefix, theirname, myname);
	if (access(readfile, EXIST))   {
		master = TRUE;
		master_channel();
		unlink(readfile);	/*  remove pipes (clever eh ?)  */
		unlink(writefile);
	}
	else   {
		master = FALSE;
		slave_channel();
	}
	connected = TRUE;
	wheader(inwin, "[Connected]\n");
	beep();
	sprintf(clock_temp, elapsed, 0, 0, 0);
	sprintf(tmp, "  %s is talking to %s (%s) ",
					myname, theirname, nameof(theirname));
        wmove(dividewin, 0, (COLS - strlen(tmp) - strlen(clock_temp)) / 2);
	wstandout(dividewin);
	waddstr(dividewin, tmp);
	getyx(dividewin, divwiny, divwinx);
	waddstr(dividewin, clock_temp);
	wstandend(dividewin);
        wrefresh(dividewin);
}



out_of_date(file_ptr)	/*  determine whether pipe file is not in use  */

char	*file_ptr;

{
	long	time();
	struct stat	stat_buf;

	if (stat(file_ptr, &stat_buf) == -1)   {   /*  assume doesn't exist  */
		return(FALSE);
	}
	if ((time((long *) 0) - stat_buf.st_mtime) > (TRIES * WAIT_TIME))   {
		return(TRUE);
	}
	return(FALSE);
}



/*  create named pipes for connection to slave and try to connect  */

master_channel()

{
	static char	*retry[] =   {
				"1st retry",
				"2nd retry",
				"3rd retry",
				"4th retry"
	};

	char	*dialup();
	int	retries, waiting;
	char	*retry_message, *dialtty, c;

	sprintf(readfile, "%s%s%s%s", tempdir, rprefix, myname, theirname);
	umask(0);
	if (mknod(readfile, FIFO) == -1)   {
		error("can't create %s", readfile);
	}
	if (mknod(writefile, FIFO) == -1)   {
		error("can't create %s", writefile);
	}
	open_channel();
	dialtty = dialup(CONNECT);
	sprintf(tmp, "[Waiting for your party to respond on %s]\n", dialtty);
	wheader(inwin, tmp);
	c = DISCONNECT;
	for (retries = 0 ; c != CONNECT  &&  retries <= RETRIES ; ++retries)   {
		for (waiting = 0 ; c != CONNECT  &&
				waiting < WAIT_TIME ; ++waiting)   {
			sleep(1);
			read(reading, &c, 1);
		}
		if (c != CONNECT  &&  retries < RETRIES)   {
			if (retries < (sizeof(retry) / sizeof(char *)))   {
				retry_message = retry[retries];
			}
			else   {
				retry_message = "Lost count !";
			}
			dialtty = dialup(CONNECT);
			sprintf(tmp, "[Ringing your party again on %s (%s)]\n",
							dialtty, retry_message);
			wheader(inwin, tmp);
		}
	}
	if (c != CONNECT)   {
		failed = TRUE;
		wheader(inwin, "[Your party would not respond]\n");
		die();
	}
}



slave_channel()		/*  form file names for slave connection  */

{
	sprintf(readfile, "%s%s%s%s", tempdir, wprefix, theirname, myname);
	sprintf(writefile, "%s%s%s%s", tempdir, rprefix, theirname, myname);
	open_channel();
	putc(CONNECT, writing);
}



open_channel()			/*  establish communications via pipes  */

{
	FILE	*fopen();

	if ((writing = fopen(writefile, "w+")) == NULL)   {
		error("can't open FIFO for writing");
	}
	setbuf(writing, (char *) 0);	/*  unbuffer stream  */
	if ((reading = open(readfile, O_NDELAY | O_RDONLY)) == -1)   {
		error("can't open FIFO for reading");
	}
}



wheader(win, message)		/*  print message at head of window  */

WINDOW	*win;
char	*message;

{
	wmove(win, 0, 0);
	wclrtoeol(win);
	waddstr(win, message);
	wrefresh(win);
}



screen_init()				/*  initialise talk windows  */

{
        int	i;

        initscr();
        inwin = newwin(INWINLINES, COLS, 0, 0);
        outwin = newwin(OUTWINLINES, COLS, INWINLINES + 1, 0);
        dividewin = newwin(1, COLS, INWINLINES, 0);
	noecho();
	nodelay(inwin, TRUE);
	cbreak();
        scrollok(inwin, TRUE);
        scrollok(outwin, TRUE);
	idlok(inwin, TRUE);
	idlok(outwin, TRUE);
        wmove(dividewin, 0, 0);
        for (i = 0 ; i < COLS ; ++i)   {
                waddch(dividewin, '-');
        }
        wnoutrefresh(inwin);	/* faster screen update (a la curses manual) */
        wnoutrefresh(outwin);
        wnoutrefresh(dividewin);
	doupdate();
}



/*  attempt to notify user that you wish to talk to him, or have given up  */

char *
dialup(status)

int	status;

{
	struct utmp	*ptr, *user, *getutent();
	void	setutent();
	long	tloc, time();
	char	*nameof(), ctime();
	FILE	*fp;
	int	found, logins;
	char	current_time[26 + 1];

	found = FALSE;
	logins = 0;
	setutent();
	while ((ptr = getutent()) != NULL)   {		/*  search for user  */
		if (strcmp(ptr->ut_user, theirname) == 0)   {
			if ( ! *theirtty)   {
				user = ptr;
				found = TRUE;
				break;
			}
			else   {
				if (strcmp(theirtty, ptr->ut_line) == 0)   {
					user = ptr;
					found = TRUE;
					break;
				}
				else  {
					/*  not used at present  */
					++logins;
				}
			}
		}
	}
	if ( ! found)   {
		sprintf(tmp, "[Your party is not logged on %s]\n",
			( ! *theirtty )  ?  "\b" : theirtty);
		wheader(inwin, tmp);
		failed = TRUE;
		die();
	}
	sprintf(tmp, "/dev/%s", user->ut_line);
	if ((fp = fopen(tmp, "w")) == NULL)   {
		wheader(inwin, "[Your party has disabled messages]\n");
		failed = TRUE;
		die();
	}
	if (status == CONNECT)   {
		fprintf(fp,
			"\r\7%s (%s) is phoning - respond with 'talk %s'    \n",
						myname, nameof(myname), myname);
	}
	else   {
		if (time(&tloc) != -1)   {
			strcpy(current_time, ctime(&tloc));
			current_time[strlen(current_time) - 1] = NULL;
		}
		else   {
			strcpy(current_time, "now");
		}
		fprintf(fp, "\r\7%s (%s) has stopped phoning [%s]\n",
					myname, nameof(myname), current_time);
	}
	fclose(fp);
	return(user->ut_line);
}



talk()				/*  talk to other user  */

{
	int	clock();

	/*  initialise window cursor positions  */
	inwinx = outwinx = outwiny = 0;
	inwiny = 1;

	delchar = erasechar();
	time_changed = FALSE;

	/*  place cursor in input window  */
	wmove(inwin, inwiny, inwinx);
	wrefresh(inwin);

	signal(SIGALRM, clock);
	alarm(CLOCK_TICK);
	while (1)   {
		/*  place cursor in input window  */
		wmove(inwin, inwiny, inwinx);
		wrefresh(inwin);
		/*  check for any activity  */
		if (input() == FALSE  &&  output() == FALSE)   {
			sleep(1);
		}

		/*******************
		*      SIGALRM was asynchronous to the rest of the program
		*   ie. it occurred whenever it liked and updated elapsed
		*   time.  This worked ok in general, although once in a while
		*   things would get confused (nothing CTRL-l couldn't
		*   overcome).  Fixed by using a global flag.
		*******************/

		if (time_changed)   {			/*  elapsed time  */
			time_changed = FALSE;
			update_time();
		}
	}
}



clock()		/*  set global flag to indicate time should be updated  */

{
	time_changed = TRUE;
	signal(SIGALRM, clock);
	alarm(CLOCK_TICK);
}



update_time()			/*  display current time on screen  */

{
	static int	elapsed_time = 0;

	elapsed_time += CLOCK_TICK;
	wmove(dividewin, divwiny, divwinx);
	wstandout(dividewin);
	wprintw(dividewin, elapsed, elapsed_time / 3600,
					elapsed_time / 60, elapsed_time % 60);
	wstandend(dividewin);
	wrefresh(dividewin);
}



input()			/*  check for and act on user input  */

{
	int	x;

	x = wgetch(inwin);
	getyx(inwin, inwiny, inwinx);
	if (x != -1)   {
		switch(x)   {
			case  END_OF_FILE  :
				waddstr(inwin, "\n[You have disconnected]");
				wrefresh(inwin);
				die();
				break;

			case  REFRESH  :
				clearok(curscr, TRUE);
				wrefresh(curscr);
				break;

			case  BELL  :
				putc(x, writing);
				break;

			default  :
				if (x != delchar)   {
					if (not_printable(x))   {
						beep();
						break;
					}
					wmove(inwin, inwiny, inwinx);
					waddch(inwin, x);
				}
				else   {
					wprevch(inwin);
					wdelch(inwin);
					x = DELETE;
				}
				getyx(inwin, inwiny, inwinx);
				wrefresh(inwin);
				putc(x, writing);
				break;
		}
		return(TRUE);
	}
	return(FALSE);
}



output()		/*  check for and act on any output to be displayed  */

{
	char	c;

	if (read(reading, &c, 1) != 0)   {
		switch (c)   {
			case  DISCONNECT  :
				waddstr(inwin,
					"\n[Your party has disconnected]");
				wrefresh(inwin);
				beep();
				die();
				break;

			case  DELETE  :
				wprevch(outwin);
				wdelch(outwin);
				break;

			case  BELL  :
				beep();
				break;

			default  :
				wmove(outwin, outwiny, outwinx);
				waddch(outwin, c);
				break;
		}
		getyx(outwin, outwiny, outwinx);
		wrefresh(outwin);
		return(TRUE);
	}
	return(FALSE);
}



/*  move cursor back to previous non-space char within window  */

wprevch(win)

WINDOW	*win;

{
	int	x, y;

	getyx(win, y, x);
	if (--x < 0)   {
		if (--y < 0)   {
			wmove(win, 0, 0);
			return;
		}
		for (x = win->_maxx - 1 ; x >= 0 ; --x)   {
			wmove(win, y, x);
			if (winch(win) != SPACE)   {
				return;
			}
		}
		return;
	}
	wmove(win, y, x);
}



char *
nameof(user)			/*  find name of user (from passwd file)  */

char	*user;

{
	struct passwd	*entry, *getpwnam();

	entry = getpwnam(user);
	if (*(entry->pw_gecos))   {
		return(entry->pw_gecos);
	}
	return("no name");
}



forced_die()			/*  program interrupted by signal  */

{
	alarm(0);		/*  disable clock to be safe  */
	forced = TRUE;
	die();
}



die()			/*  gracefully exit program  */

{
	char	*dialup();

	alarm(0);
	signal(SIGALRM, SIG_IGN);
	signal(SIGINT, SIG_IGN);

	if (writing != NULL)   {
		putc(DISCONNECT, writing);
		fclose(writing);
	}
	if (reading != -1)   {
		close(reading);
	}
	if (master  &&  (! connected))   {
		unlink(readfile);
		unlink(writefile);
	}

	wmove(outwin, OUTWINLINES - 1, 0);
	if (forced)   {
		sprintf(tmp, "[Forced exit%s] ",
			(connected)  ?  " (disconnected)" : "");
		waddstr(outwin, tmp);
	}
        wrefresh(outwin);
	flushinp();
	nodelay(inwin, FALSE);
	delwin(inwin);
	delwin(outwin);
	delwin(dividewin);
	endwin();
	if (failed)   {
		exit(4);
	}
	if (! connected)   {
		dialup(DISCONNECT);
	}
	putchar('\n');
	exit(0);
}



/*  print error message and terminate program (via die())  */
error(message, arg1, arg2)

char	*message, *arg1, *arg2;

{
	wmove(outwin, OUTWINLINES - 2, 0);
	wprintw(outwin, "%s: ", progname);
	wprintw(outwin, message, arg1, arg2);
	wrefresh(outwin);
	failed = TRUE;
	die();
}
SHAR_EOF
exit 0
#
#	End of shell archive
#

jrg@hpirs.HP (Jeff Glasson) (12/18/86)

Talk is part of the standard 4.2 distribution.  If you have 4.2 on
some machines there, you should already have the source.  If not,
talk is covered by lisencing restrictions and the source is not
freely available.

Note: talk relies heavily on the 4.2 socket system for interprocess
communication.  System V does not have this facility.  the 4.2 talk
would require major modifications to run under system V.

However, there is some good news... Several version of talk for
System V have been posted to net.sources or mod.sources in the past.
See if you have them laying around somewhere.

Jeff Glasson
Hewlett-Packard
{ucbvax,hplabs}!hpda!jrg