kdavis@lamc.UUCP (08/31/87)
#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();
}
--
Ken Davis - {ptsfa,well,hoptoad}!lamc!kdavis ptsfa!lamc!kdavis@sun.com