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