ka@spanky.UUCP (06/06/83)
# The vnews source has been posted in three pieces. The one titled # "vnews source -- visual.c" contains the file visual.c. This file # contains all the rest of the source files in shell archive format. # (Simply delete the article header and feed it into the shell.) The # article entitled "Vnews User Manual" describes how to use vnews. cat > README <<\! 1. Terminal Handling Dealing with various terminals is a major headache. Vnews will support any of three tarminal interfaces; you get your choice. 1. You can use the terminal handle provided in virtterm.c. The problem with this is that the code is written for a HP2621 terminal; if you have a different terminal you will have to hack up the code. The file convert.term explains how to do this. I have already done this for the IBM PC Asynchronous Communications Package; let me know if you are interested and I will mail you a copy. If you have another terminal with destructive tabs, also ask me for this; you can lift some code from it. 2. You can use Mark Horton's version of curses. There are several problems with this. Mark's curses are not available outside Bell Labs. You can't use curses on a PDP-11; it won't fit. Commands typed during the middle of a screen update won't (normally) interrupt the update. There are problems with handling certain terminals--the IBM PC termcap entry broke both Mark Horton's and Ken Arnold's versions of curses. 3. You can use Ken Arnold's version of curses. This was posted to net.sources a few days ago so everybody should have it, but the other problems with Mark's curses apply here as well. (Actually, you might be able to get it to fit on a PDP-11 if you trimmed readnews a bit; I haven't tried.) In addition, Ken Arnold's version of curses doesn't use line insert and delete. 2. Auxiliary files If that hasn't scared you off, the first step is to install the files vnews.help, reply.sh, and follow.sh. Vnews.help is the vnews help file. It is normally installed as "/usr/lib/news/vnews.help"; if you install it elsewhere then definition of VHELP in visual.c. Reply.sh and follow.sh are shell procedures which handle replies and followups, respectively. In case you want to change these, the arguments to reply are the name of a temp file containing the header, the name of the file containing the article, and the author of the article. The arguments to follow are the same except that the last argument contains the newsgroup(s) the reply is to be posted to. You will have to change reply if the program recmail (delivered with 2.10) isn't in your path. These programs are normally installed as "/usr/lib/news/reply" and "/usr/lib/news/follow"; if you install them elsewhere change the definitions of REPLYPROG and FOLLOWPROG in visual.c 3. Mail format In 2.10 Mark moved the routine save out of readr.c even though it writes to the terminal, so I had to copy it. If you made any local changes to this routine (presumably to support a different mail file format), you will have to duplicate the change in the routine vsave. 4. Compiling vnews Vnews uses all the object modules used in readnews except for readr.c, which is replaced with visual.c. Therefore, if you have write permission on the netnews source directory, you may want to build vnews there. Otherwise copy all of the ".o" files out of the netnews directory into the vnews directory. The vnews makefile is intended to be concatenated onto the end of the netnews makefile. Once this is done, "make hpvnews" will compile vnews using the virtterm package, "make hvnews" will compile vnews using Mark Horton's curses, and "make avnews" will compile vnews using Ken Arnold's curses. The shell procedure mkvnews will compile vnews and install it in /usr/bin. Modify the assignment to "type" to have it compile the version of vnews that you desire. ! cat > convert.term <<\! HOW TO CONVERT VIRTTERM TO RUN WITH A DIFFERENT TERMINAL TYPE: I recomended that you make the minimum number of changes neces- sary to make the code work, and worry about efficiency only after you have something running. I will describe the routines which you will probably have to change, but first a few conventions should be described. All these routines should perform output by calling _putstr to output a string or putch to output a single character. The current location of the cursor on the screen is always stored in the variables _srow and _scol; any routine with moves the cursor must update them. Most of the terminal escape sequences are de- fined at the top of the program. You should change SCLEAR to a sequence which will clear the screen and leave the cursor in the upper left hand corner. As a matter of style, you will probably want to delete or modify the other sequences. _amove moves the cursor to the specified row and column. If your terminal has an escape sequence to move to an arbitrary location on the screen, just use that as a first crack. Although this is nominally an "internal" routine, visual cheats and calls it any- way, so you must provide it. The flag _curjunked indicates that the values in _srow and _scol do not reflect the actual location of the cursor; vnews doesn't use it. In the HP version, the subroutines _relmove and _rmcost are used by _amove to help find an optimum sequence to move the cursor. _relmove moves the cursor between two points using only relative motions, and _rmcost returns the cost of doing so. You should be able to convert this code with little effort. Just delete the code for sequences (like tab and backtab) which you don't have. _aputc outputs a character at the current cursor position. This routine may have to be modified to update _srow and _scol correctly when a character is written in the last column of a line. The HP code will allow a character to be written to the bottom right hand corner of the screen, but if your terminal does not make that easy then simply check for _srow==PAGWID-1 && _scol==PAGLEN-1 and return without writing the character. _setul turns underlining mode on and off. If your terminal doesn't support underlining, make this routine a no-op. Current- ly, vnews only uses underlining to indicate that the article con- tains overstrikes, so you may not want to bother with underlining immediately. dshift and ushift scroll the article. If your terminal has line insert/delete, simply update the definitions of LINSERT and LIN- DEL the top of the program. If these aren't defined at all, you will get a rather shoddy version that will work on many termi- nals. If you convert ushift into a no-op, you should remove the "#define HASSCROLL" line in visual.c. botscreen moves to one line below the bottom of the screen. The version provided will work on most terminals. _clrtoeol clears everything between the specified locaion and the end of the line. If your terminal has "clear to end of line," simply update the definition of SCLRLIN. If not, use the version which has been ifdeffed out. _chkclrline is used on the HP to decide whether to output a clear line sequence before updating a line. For a first pass, simply ifdef out the entire body of this routine. ! cat > mkvnews <<\! type=hpnews make $type echo cp $type /usr/bin/vnews cp $type /usr/bin/vnews ! cat > vnews.mk <<\! # Vnews section of makefile # This is set up to allow you to make multiple version of vnews. # You can strip it down for your system. RDOBJECTS = readnews.o rfuncs.o rfuncs2.o rextern.o process.o rpathinit.o $(OBJECTS) VOBJECTS = $(RDOBJECTS) visual.o HPOBJECTS = $(VOBJECTS) virtterm.o PCOBJECTS = $(VOBJECTS) pcterm.o HOBJECTS = $(RDOBJECTS) hvisual.o curterm.o AOBJECTS = $(RDOBJECTS) avisual.o acurterm.o # Normal vnews (with virtterm interface) hpvnews: $(HPOBJECTS) $(CC) $(LFLAGS) $(HPOBJECTS) -o $@ # Vnews with IBM PC terminal handler # Contact spanky!ka if you want this. pcnews: $(PCOBJECTS) $(CC) $(LFLAGS) $(PCOBJECTS) -o $@ # Vnews with Mark Horton's curses hvnews: $(HOBJECTS) $(CC) $(LFLAGS) $(HOBJECTS) -lcurses -o $@ # Vnews with Ken Arnold's curses avnews: $(AOBJECTS) $(CC) $(LFLAGS) $(AOBJECTS) -lcurses -ltermcap -o $@ # dependencies visual.o: rparams.h defs.h params.h hvisual.o: visual.c rparams.h defs.h params.h cp visual.c hvisual.c $(CC) -c $(CFLAGS) -DHCURSES hvisual.c rm hvisual.c avisual.o: visual.c rparams.h defs.h params.h cp visual.c avisual.c $(CC) -c $(CFLAGS) -DACURSES avisual.c rm avisual.c acurterm.o: curterm.c cp curterm.c acurterm.c $(CC) -c $(CFLAGS) -DACURSES acurterm.c rm acurterm.c # Install. You may have to modify this to work on your system. vcp: all cp vnews $(BINDIR)/vnews chown $(NEWSUSER) $(BINDIR)/vnews chmod 755 $(BINDIR)/vnews cp vnews.help $(LIBDIR) cp reply.sh $(LIBDIR)/reply cp follow.sh $(LIBDIR)/follow clean: -rm -f visual.o hvisual.o avisual.o virtterm.o curterm.o acurterm.o ! cat > virtterm.c <<\! /* * Virtual terminal handler for the HP-2621 terminal. * Written by Kenneth Almquist, AGS Computers (HO 4C601, X7105). */ #include <stdio.h> #define PAGLEN 24 #define BOTLINE (PAGLEN - 1) #define PAGWID 80 #define DIRTY 01 /* terminal escape sequences */ #define SHOME "\033H" /* move cursor to (0, 0) */ #define HOMELEN 2 /* length of SHOME */ #define SCLEAR "\033H\033J" /* clear screen and move cursor to (0, 0) */ #define SCLRLINE "\033K" /* clear to end of line */ #define CLRLINELEN 2 /* length of SCLRLINE */ #define SUP "\033A" /* move cursor up one line */ #define SBKTAB "\033i" /* back tab */ #define SLINSERT "\033L" /* insert line */ #define SLINDEL "\033M" /* delete line */ #define ULON "\033&dD" /* turn underlining on */ #define ULOFF "\033&d@" /* turn underlining off */ #define ULINE 0200 #define CURSEEN 1 #define putch(c) vputc(c) /* Constants accessable by user */ int hasscroll = 1; /* terminal has line insert/delete */ int LINES = PAGLEN; /* number of lines on screen */ int COLS = PAGWID; /* width of screen */ struct line { char len; char flags; char l[PAGWID]; }; int _row, _col; int _srow, _scol; struct line _virt[PAGLEN], _actual[PAGLEN]; int _uline = 0; int _junked = 1; int _curjunked; int _dir = 1; /* * Scrolling commands. These have immediate effect on the screen. * It is up to the caller to decide whether scrolling will help. */ #ifdef SLINSERT /* version using line insert/delete */ dshift(top, bot, count) { register i; if (count < 0) { ushift(top, bot, -count); return; } if (_junked || count >= bot - top) return; for (i = bot - count ; _actual[i].len == 0 ; i--) if (i == top) return; for (i = top ; i <= bot ; i++) _virt[i].flags |= DIRTY; for (i = bot ; i >= top + count ; i--) _actual[i] = _actual[i - count]; for ( ; i >= top ; i--) _actual[i].len = 0; _amove(bot - count + 1, 0); for (i = count ; --i >= 0 ; ) _putstr(SLINDEL); _amove(top, 0); for (i = count ; --i >= 0 ; ) _putstr(SLINSERT); _dir = -1; } ushift(top, bot, count) { register i; if (count < 0) { dshift(top, bot, -count); return; } if (_junked || count >= bot - top) return; for (i = top + count ; _actual[i].len == 0 ; i++) if (i == bot) return; for (i = top ; i <= bot ; i++) _virt[i].flags |= DIRTY; for (i = top ; i <= bot - count ; i++) _actual[i] = _actual[i + count]; for ( ; i <= bot ; i++) _actual[i].len = 0; _amove(top, 0); for (i = count ; --i >= 0 ; ) _putstr(SLINDEL); _amove(bot - count + 1, 0); for (i = count ; --i >= 0 ; ) _putstr(SLINSERT); } #else dshift(top, bot, count) { if (count < 0) { ushift(top, bot, -count); return; } /* downward shift not implemented */ } ushift(top, bot, count) { register i; if (count < 0) { dshift(top, bot, -count); return; } if (_junked || count >= bot - top) return; for (i = top + count ; _actual[i].len == 0 ; i++) if (i == bot) return; /* we cheat and shift the entire screen */ /* be sure we are shifting more lines into than out of position */ if ((bot - top + 1) - count <= PAGLEN - (bot - top + 1)) return; for (i = 0 ; i <= BOTLINE ; i++) _virt[i].flags |= DIRTY; for (i = 0 ; i <= BOTLINE - count ; i++) _actual[i] = _actual[i + count]; for ( ; i <= BOTLINE ; i++) _actual[i].len = 0; _amove(BOTLINE, 0); for (i = 0 ; i < count ; i++) putch('\n'); } #endif LINSERT /* * generate a beep on the terminal */ beep() { putch('\7'); } /* * Move to one line below the bottom of the screen. */ botscreen() { _amove(BOTLINE, 0); putch('\n'); vflush(); } move(row, col) { if (row < 0 || row >= PAGLEN || col < 0 || col >= PAGWID) return; _row = row; _col = col; } /* * Output string at specified location. */ mvaddstr(row, col, str) char *str; { move(row, col); addstr(str); } addstr(s) char *s; { register char *p; register struct line *lp; register int col = _col; lp = &_virt[_row]; if (lp->len < col) { p = &lp->l[lp->len]; while (lp->len < col) { *p++ = ' '; lp->len++; } } for (p = s ; *p != '\0' ; p++) { if (*p == '\n') { lp->len = col; lp->flags |= DIRTY; col = 0; if (++_row >= PAGLEN) _row = 0; lp = &_virt[_row]; } else { lp->l[col] = *p; lp->flags |= DIRTY; if (++col >= PAGWID) { lp->len = PAGWID; col = 0; if (++_row >= PAGLEN) _row = 0; lp = &_virt[_row]; } } } if (lp->len <= col) lp->len = col; _col = col; } addch(c) { register struct line *lp; register char *p; lp = &_virt[_row]; if (lp->len < _col) { p = &lp->l[lp->len]; while (lp->len < _col) { *p++ = ' '; lp->len++; } } lp->l[_col] = c; if (lp->len == _col) lp->len++; if (++_col >= PAGWID) { _col = 0; if (++_row >= PAGLEN) _row = 0; } lp->flags |= DIRTY; } clrtoeol() { register struct line *lp; lp = &_virt[_row]; if (lp->len > _col) { lp->len = _col; lp->flags |= DIRTY; } } /* * Clear an entire line. */ clrline(row) { register struct line *lp; lp = &_virt[row]; if (lp->len > 0) { lp->len = 0; lp->flags |= DIRTY; } } clear() { erase(); _junked++; } erase() { register i; for (i = 0 ; i < PAGLEN ; i++) { _virt[i].len = 0; _virt[i].flags |= DIRTY; } } refresh() { register i; int j, len; register char *p, *q; if (checkin()) return; if (_junked) { _sclear(); _junked = 0; } _fixlines(); for (i = _dir > 0? 0 : BOTLINE ; i >= 0 && i < PAGLEN ; i += _dir) { if ((_virt[i].flags & DIRTY) == 0) continue; _ckclrlin(i); /* decide whether to do a clear line */ len = _virt[i].len; if (_actual[i].len < len) len = _actual[i].len; p = _virt[i].l; q = _actual[i].l; for (j = 0 ; j < len ; j++) { if (*p != *q) { _amove(i, j); _aputc(*p); *q = *p; } p++, q++; } len = _virt[i].len; if (_actual[i].len > len) { _clrtoeol(i, len); } else { for ( ; j < len ; j++) { if (*p != ' ') { _amove(i, j); _aputc(*p); } *q++ = *p++; } _actual[i].len = len; } if (checkin()) return; } _dir = 1; if (CURSEEN) _amove(_row, _col); vflush(); /* flush output buffer */ } _sclear() { register struct line *lp; _putstr(SCLEAR); _srow = _scol = 0; for (lp = _actual ; lp < &_actual[PAGLEN] ; lp++) { lp->len = 0; } for (lp = _virt ; lp < &_virt[PAGLEN] ; lp++) { if (lp->len != 0) lp->flags |= DIRTY; } } #ifdef notdef /* included to simplify conversion to new terminal */ _clrtoeol(row, col) { register struct line *lp = &_actual[row]; register i; for (i = col ; i < lp->len ; i++) { if (lp->l[i] != ' ') { _amove(row, i); _aputc(' '); } } lp->len = col; } #else /* HP version */ _clrtoeol(row, col) { _amove(row, col); if (_actual[row].len == col + 1) _aputc(' '); else _putstr(SCLRLINE); _actual[row].len = col; } #endif _fixlines() { register struct line *lp; register char *p; register int i; for (i = 0 ; i < PAGLEN ; i++) { lp = &_virt[i]; if (lp->flags & DIRTY) { lp = &_virt[i]; for (p = &lp->l[lp->len] ; --p >= lp->l && *p == ' ' ; ); lp->len = p + 1 - lp->l; if (lp->len == _actual[i].len && strncmp(lp->l, _actual[i].l, lp->len) == 0) lp->flags &=~ DIRTY; } } } _ckclrlin(i) { int eval; int len; int first; register struct line *vp, *ap; register int j; ap = &_actual[i]; vp = &_virt[i]; len = ap->len; eval = -2; if (len > vp->len) { len = vp->len; eval = 0; } for (j = 0 ; j < len && vp->l[j] == ap->l[j] ; j++); if (j == len) return; first = j; while (j < len) { if (vp->l[j] == ' ') { if (ap->l[j] != ' ') { while (++j < len && vp->l[j] == ' ' && ap->l[j] != ' ') { eval++; } if (j == len) eval++; continue; } } else { if (vp->l[j] == ap->l[j]) { while (++j < len && vp->l[j] == ap->l[j]) { eval--; } continue; } } j++; } for (j = first ; --j >= 0 ; ) if (vp->l[j] != ' ') break; if (j < 0) first = 0; if (eval > 0) { _amove(i, first); _putstr(SCLRLINE); _actual[i].len = first ; } } _amove(row, col) { char s[20]; int cost, i; if (row == _srow && col == _scol && _curjunked == 0) return; _setul(0); /* Warning: omitting the row isn't documented; it seems to work */ if (row == _srow) sprintf(s, "\033&a%dC", col); else if (row == _srow + 1) sprintf(s, "\n\033&a%dC", col); else sprintf(s, "\033&a%dy%dC", row, col); cost = strlen(s); if (_curjunked == 0 && (i = _rmcost(_srow, _scol, row, col)) < cost) { _relmove(s, _srow, _scol, row, col); cost = i; } if (_curjunked == 0 && col < _scol) { if (_scol < 72 || _srow <= row) { if ((i = _rmcost(_srow, 0, row, col) + 1) < cost) { s[0] = '\r'; _relmove(s + 1, _srow, 0, row, col); cost = i; } } else { if ((i = _rmcost(_srow + 1, 0, row, col) + 1) < cost) { s[0] = '\t'; _relmove(s + 1, _srow + 1, 0, row, col); cost = i; } } } if (row < cost && (i = _rmcost(0, 0, row, col) + HOMELEN) < cost) { strcpy(s, SHOME); _relmove(s + HOMELEN, 0, 0, row, col); cost = i; } _putstr(s); _srow = row, _scol = col; _curjunked = 0; } _rmcost(orow, ocol, nrow, ncol) { char s[200]; _relmove(s, orow, ocol, nrow, ncol); return strlen(s); } _relmove(s, orow, ocol, nrow, ncol) char *s; { register char *p, *q; int tab; p = s; while (orow < nrow) { /* cursor down */ *p++ = '\n'; orow++; } while (orow > nrow) { /* cursor up */ for (q = SUP ; *p = *q++ ; p++); orow--; } if (ocol < ncol) { /* tab (nondestructive) */ while ((tab = (ocol + 8) &~ 07) <= ncol) { *p++ = '\t'; ocol = tab; } if (tab < PAGWID && tab - ncol < ncol - ocol) { *p++ = '\t'; ocol = tab; } } else if (ocol > ncol) { /* backwards tab */ while ((tab = (ocol - 1) &~ 07) >= ncol) { if (tab == ocol - 1) *p++ = '\b'; else for (q = SBKTAB ; *p = *q++ ; p++); ocol = tab; } if (ncol - tab + 1 < ocol - ncol) { for (q = SBKTAB ; *p = *q++ ; p++); ocol = tab; } } while (ocol > ncol) { /* cursor left */ *p++ = '\b'; ocol--; } if (ocol < ncol) { /* cursor right */ register struct line *lp = &_actual[nrow]; while (ocol < ncol) { if (ocol < lp->len) *p++ = lp->l[ocol]; else *p++ = ' '; ocol++; } } *p++ = '\0'; } _aputc(c) { _setul(c & ULINE); putch(c &~ ULINE); if (++_scol >= PAGWID) { _scol = 0; if (++_srow >= PAGLEN) { _srow = 0; _putstr(SHOME); } } } _setul(on) { if (on) { if (_uline == 0) { _putstr(ULON); _uline = 1; } } else { if (_uline != 0) { _putstr(ULOFF); _uline = 0; } } } _putstr(s) char *s; { register char *p; for (p = s ; *p ; p++) { putch(*p); } } ! cat > reply.sh <<\! trap : 2 3 /bin/cp $1 /tmp/mailr$$a A=$2 ${EDITOR-ed} $1 $2 if /bin/cmp -s $1 /tmp/mailr$$a then rm -f $1 /tmp/mailr$$a exit 22 fi ( trap '' 1 2 3 recmail <$1 x=$? if test $x != 0 then echo 'Subject: reply command failed\n' | \ cat - /tmp/mailr$$b | mail "${LOGNAME-$USER}" fi rm -f $1 /tmp/mailr$$a /tmp/mailr$$b ) > /tmp/mailr$$b 2>&1 & exit 0 ! cat > follow.sh <<\! trap : 2 3 /bin/cp $1 /tmp/fol$$a A=$2 ${EDITOR-ed} $1 $2 if /bin/cmp -s $1 /tmp/fol$$a then /bin/rm -f $1 /tmp/fol$$a exit 22 fi ( trap '' 1 2 3 inews -h < $1 x=$? /bin/sleep 60 # wait for inews to finish if test -s /tmp/fol$$b -o $x != 0 then ( /bin/echo 'Subject: followup failed\n' /bin/cat /tmp/fol$$b /bin/echo '\nYour article follows:' /bin/cat $1 ) | mail "${LOGNAME-$USER}" fi /bin/rm -f $1 /tmp/fol$$a /tmp/fol$$b ) > /tmp/fol$$b 2>&1 & exit 0 ! cat > vnews.help <<\! Vnews commands: (each may be preceded by a non-negative ) CR Next page or article D Decrypt a rot 13 joke n Go to next article A Go to article numbered count e Mark current article as unread < Go to article with given ID + Go forwards count articles p Go to parent article b Go to previous article ug Unsubscribe to this group ^B Go backwards count pages ^L Redraw screen ^N Go forward count lines v Printf netnews version ^P Go backwards count lines q Quit ^D Go forward half a page ^\ Quit without updating .newsrc h Display article header ? Display this message ! Escape to shell r Reply to article f Post a followup article l Display article (use after !, r, f, or ?) s Save article in file w Save without header c Cancel the current article N Advance to newsgroup (next is default) [Press l to see article again] !