[comp.sources.misc] v05i019: grabchars, get keystrokes directly from user

daniel@unicom.UUCP (Dan Smith "vote early and often...") (10/28/88)

Posting-number: Volume 5, Issue 19
Submitted-by: "Dan Smith "vote early and often..."" <daniel@unicom.UUCP>
Archive-name: grabchars

	"grabchars" gets one or more keystrokes from the user, without
requiring them to hit return.  It was written to make shell scripts
(doesn't matter what type) more interactive.

	I know that it works fine on Suns running SUN OS 3.2-3.5,
and a Vax 11/750 running Mt. Xinu 4.3 BSD.

	You'll find uses for this in all sorts of places.  The prime
candidate is in your .login file, it's easy to use this to select different
options.  I've provided a "demo" csh script which runs through many of the
options.  See the README for more...

				dan

dan smith, island graphics, marin co, ca| +1 (415) 491 1000(W), 332 FAST(H)
4000 civic center dr, san rafael 94903  | dis: they're solely my opinions
daniel@island.uu.net {ucbvax!ucbcad,sun}!island!daniel pacbell!unicom!daniel
I'd rather have Roosevelt in a wheelchair, than Reagan & Bush on a horse -Jesse

#! /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
#	TODO
#	Makefile
#	grabchars.c
#	patchlevel.h
#	grabchars.1
#	demo
# This archive created: Wed Oct 26 21:08:10 1988
# By:	Dan Smith "vote early and often..." ()
export PATH; PATH=/bin:/usr/bin:$PATH
echo shar: "extracting 'README'" '(2463 characters)'
if test -f 'README'
then
       echo shar: "will not over-write existing file 'README'"
else
cat << \SHAR_EOF > 'README'

	"grabchars" gets one or more keystrokes from the user, without
requiring them to hit return.  It was written to make shell scripts
(doesn't matter what type) more interactive.

	I know that it works fine on Suns running SUN OS 3.2-3.5,
and a Vax 11/750 running Mt. Xinu 4.3 BSD.  You should be able to type
"make depend", followed by "make" or "make install".  I haven't tried
this on a Sys V machine.  If you do, and it works, let me know.  You will
need getopt (3) to compile this program.  If you're not sure if you
have this, try "nm /lib/libc.a | grep getopt".  Get a Public Domain
version if you don't (write me if you get really stuck, I have it).

	You'll find uses for this in all sorts of places.  The prime
candidate is in your .login file, it's easy to use this to select different
options.  I've provided a "demo" csh script which runs through many of the
options.

	If you make any changes, *please* send me diffs!

	This is in the public domain, don't make money off of it,
and don't pretend you wrote it, and I'll be happy :-)

	Have fun!

				dan

	Usage rundown:

	grabchars			gets one keystroke
	grabchars -c<valid characters>  only <valid chars> are returned
	grabchars -e			output to stderr instead of stdout
	grabchars -p<prompt>		prompt to help user
	grabchars -q<prompt>		prompt to help user (through stderr)
	grabchars -n<number>		number of characters to read
	grabchars -t<seconds>		timeout after <seconds>

	examples: (values to arguments can be in the same word or the next one)

	grabchars -caeiou	 or
	grabchars -c aeiou		get one of the vowels
	grabchars -c i			get the letter 'i'
	grabchars '-penter a letter '	print the prompt "enter a letter "
	grabchars '-qenter a letter '	print the prompt ('q' for question)
					"enter a letter " through stderr...
	grabchars -n4			get four characters
	grabchars -t2			timeout after two seconds

	print a prompt and grab three characters...
	grabchars -p 'enter three characters >> ' -n 3

	get two numbers with a ten second timeout...
	grabchars -c 0123456789 -n2 -t10

	note that arguments like "-n4" or "-n 4" are handled the same way

	grabchars -h			will give a usage screen...

dan smith, island graphics, marin co, ca| +1 (415) 491 1000(W), 332 FAST(H)
4000 civic center dr, san rafael 94903  | dis: they're solely my opinions
daniel@island.uu.net {ucbvax!ucbcad,sun}!island!daniel pacbell!unicom!daniel
I'd rather have Roosevelt in a wheelchair, than Reagan & Bush on a horse -Jesse
SHAR_EOF
fi
echo shar: "extracting 'TODO'" '(496 characters)'
if test -f 'TODO'
then
       echo shar: "will not over-write existing file 'TODO'"
else
cat << \SHAR_EOF > 'TODO'
TODO file...

	add "-s" option (silent)

	so that we can just work off of the return value... this is cinchy,
I just haven't gotten to it yet, but I want to get this out.


	add support for arrow keys and other special keys...

	I have the code for this in my "phonemail" program (to be
	posted soon).  I'm not sure what would constitute reasonable
	return values (have negative values? values greater than 128?)
	Is there a canonical return value list for keyboard events?

	Thatzit for now...

SHAR_EOF
fi
echo shar: "extracting 'Makefile'" '(2122 characters)'
if test -f 'Makefile'
then
       echo shar: "will not over-write existing file 'Makefile'"
else
cat << \SHAR_EOF > 'Makefile'
#
#       Makefile for grabchars
#       
#       10/88    dan smith, daniel@island.uu.net
#
SRCS =	grabchars.c
OBJS =	grabchars.c
HDRS =	patchlevel.h
DESTBIN = /usr/public/bin
DESTMAN = /usr/public/man/man1

CC = cc
CFLAGS = -O

.c.o:
	$(CC) -c $(CFLAGS) $(DEFS) $*.c

all: grabchars

grabchars: $(OBJS)
	touch grabchars
	mv grabchars grabchars.old
	@echo starting...
	$(CC) $(CFLAGS) $(OBJS) -o grabchars
	@echo done...

pgrind:
	@echo pgrinding out sources...
	lpq $(PRINTER)
	pgrind $(SRCS) $(HDRS) Makefile

shar:
	@echo bundling up grabchars for transit...
	shar -v README TODO Makefile $(SRCS) $(HDRS) grabchars.1 demo > sendme

clean:
	touch $(OBJS) grabchars grabchars.old
	- /bin/rm $(OBJS) grabchars grabchars.old

install: grabchars
	- cp grabchars $(DESTBIN)
	- cp grabchars.1 $(DESTMAN)
	@echo formatting man page...
	man grabchars

depend: 
	@echo making dependencies...
	sed -n '1,/^#	lines after this point/p' Makefile >.depends &&\
	cc -M $(SRCS) >> .depends && mv .depends Makefile

tags: $(SRCS)
	ctags $(SRCS)

#       lines after this point produced with cc -M, leave this line here
grabchars.o: grabchars.c
grabchars.o: /usr/include/stdio.h
grabchars.o: /usr/include/signal.h
grabchars.o: /usr/include/sgtty.h
grabchars.o: /usr/include/sys/ioctl.h
grabchars.o: /usr/include/sys/ttychars.h
grabchars.o: /usr/include/sys/ttydev.h
grabchars.o: /usr/include/ctype.h
grabchars.o: /usr/include/string.h
grabchars.o: grabchars.c
grabchars.o: /usr/include/stdio.h
grabchars.o: /usr/include/signal.h
grabchars.o: /usr/include/sgtty.h
grabchars.o: /usr/include/sys/ioctl.h
grabchars.o: /usr/include/sys/ttychars.h
grabchars.o: /usr/include/sys/ttydev.h
grabchars.o: /usr/include/ctype.h
grabchars.o: /usr/include/string.h
grabchars.o: /usr/include/strings.h
grabchars.o: grabchars.c
grabchars.o: /usr/include/stdio.h
grabchars.o: /usr/include/signal.h
grabchars.o: /usr/include/sgtty.h
grabchars.o: /usr/include/sys/ioctl.h
grabchars.o: /usr/include/sys/ttychars.h
grabchars.o: /usr/include/sys/ttydev.h
grabchars.o: /usr/include/ctype.h
grabchars.o: /usr/include/string.h
grabchars.o: /usr/include/strings.h
SHAR_EOF
fi
echo shar: "extracting 'grabchars.c'" '(4787 characters)'
if test -f 'grabchars.c'
then
       echo shar: "will not over-write existing file 'grabchars.c'"
else
cat << \SHAR_EOF > 'grabchars.c'
/*
**	grabchars.c	- get characters directly from the user
**
**	October 23, 1988, Dan Smith (daniel@island.uu.net)
**
**	This program grabs characters from the user as they are
**	typed in, without having to wait for the return key to
**	be pressed.  Among other things, this allows shell scripts
**	to be written with highly interactive menus...
**
**	Usage rundown:
**
**	grabchars			gets one keystroke
**	grabchars -c<valid characters>  only <valid chars> are returned
**	grabchars -e			output to stderr instead of stdout
**	grabchars -p<prompt>		prompt to help user
**	grabchars -q<prompt>		prompt to help user (through stderr)
**	grabchars -n<number>		number of characters to read
**	grabchars -t<seconds>		timeout after <seconds>
**
**	examples: (values to arguments can be in the same word or the next one)
**
**	grabchars -caeiou	 or
**	grabchars -c aeiou		get one of the vowels
**	grabchars -c i			get the letter 'i'
**	grabchars '-penter a letter '	print the prompt "enter a letter "
**	grabchars '-qenter a letter '	print the prompt ('q' for question)
**					"enter a letter " through stderr...
**	grabchars -n4			get four characters
**	grabchars -t2			timeout after two seconds
**
**	print a prompt and grab three characters...
**	grabchars -p 'enter three characters >> ' -n 3
**
**	get two numbers with a ten second timeout...
**	grabchars -c 0123456789 -n2 -t10
**
**	note that arguments like "-n4" or "-n 4" are handled the same way
*/

#include <stdio.h>
#include <signal.h>
#include <sgtty.h>
#include <ctype.h>
#include <string.h>

struct sgttyb orig, new;

int exit_stat;

char *usage_statement[] = {
"usage:",
"grabchars			gets one keystroke",
"	-c<valid characters>	only <valid chars> are returned",
"	-e			output to stderr instead of stdout",
"	-p<prompt>		prompt to help user",
"	-q<prompt>		prompt to help user (through stderr)",
"	-n<number>		number of characters to read",
"	-t<seconds>		timeout after <seconds>",
" ",
"examples: (values to arguments can be in the same word or the next one)",
" ",
"grabchars -caeiou	 or",
"grabchars -c aeiou		get one of the vowels",
"grabchars -c i			get the letter 'i'",
" ",
"grabchars '-penter a letter '	print the prompt \"enter a letter \"",
"grabchars -n4			get four characters",
"grabchars -t2			timeout after two seconds",
" ",
"print a prompt and grab three characters...",
"grabchars -p 'enter three characters >> ' -n 3",
0
};

main (argc, argv)
int argc;
char **argv;
{
	extern int optind, opterr;
	extern char *optarg;

	int how_many = 1, check_flag = 0;
	int i;
	int timeout;
	char ch;
	char comarg;
	char valid_chars[128];
	FILE *outfile = stdout;

	int lets_go (), overtime ();

	/* handle the outside world */
	signal (SIGINT, lets_go);
	signal (SIGTSTP, lets_go);
	signal (SIGQUIT, lets_go);
	alarm (0);

	opterr = 0;
	exit_stat = -1;	/* if we're interrupted, exit with this status */

	while ((comarg = getopt (argc, argv,  "ec:n:p:q:t:")) != EOF) {
		switch (comarg) {
			case 'c':
				check_flag = 1;
				strcpy (valid_chars, optarg);
				break;
			case 'e':
				outfile = stderr;
				break;
			case 'n':
				how_many = atoi (optarg);
				if (how_many <= 0) {
					fprintf (stderr, "number of characters to read must be greater than zero\n");
					exit (-1);
				}
				break;
			case 'p':
				fprintf (stdout, "%s", optarg);
				break;
			case 'q':
				fprintf (stderr, "%s", optarg);
				break;
			case 't':
				timeout = atoi (optarg);
				if (timeout <= 0) {
					fprintf (stderr, "number of seconds to timeout must be greater than zero\n");
					exit (-1);
				}

				/*
				** we must have some valid time >0 seconds to
				** get here, so we'll set an alarm...
				*/
				signal (SIGALRM, overtime);
				alarm ((unsigned int) timeout);
				break;

				/*
				** I bet I could leave out "default", but
				** I also bet that all getopt () routines
				** are not created equal, so in it stays!
				*/
			case '?':
			default:
				i = 0;
				while (usage_statement[i])
					puts (usage_statement[i++]);
				exit (-1);
		}
	}

	/*	play havoc with the terminal :-) */
	ioctl (0, TIOCGETP, &orig);
	new = orig;
	new.sg_flags &= ~ECHO;
	new.sg_flags |= CBREAK;
	ioctl (0, TIOCSETP, &new);

	for (i = 0; i < how_many; i++) {
		ch = getchar ();
		if (check_flag)
			if ( ! (any (ch, valid_chars))) {
				i--;
				continue;
			}
		putc (ch, outfile);
	}
	exit_stat = i;
	lets_go ();
}

/*
**	something's up with the user...give a useful exit status so
**	we can ask things like "do you need help?"
*/
int overtime ()
{
	exit_stat = -2;
	lets_go ();
}

/*	clean up and get out of here... */
int lets_go ()
{
	ioctl (0, TIOCSETP, &orig);
	exit (exit_stat);
}

/*
**      do any chars "s" match 'c'?
*/
any (c, s)
register int c;
register char *s;
{
	while (*s)
		if (*s++ == c)
			return (1);
	return (0);
}
SHAR_EOF
fi
echo shar: "extracting 'patchlevel.h'" '(21 characters)'
if test -f 'patchlevel.h'
then
       echo shar: "will not over-write existing file 'patchlevel.h'"
else
cat << \SHAR_EOF > 'patchlevel.h'
#define PATCHLEVEL 0
SHAR_EOF
fi
echo shar: "extracting 'grabchars.1'" '(3009 characters)'
if test -f 'grabchars.1'
then
       echo shar: "will not over-write existing file 'grabchars.1'"
else
cat << \SHAR_EOF > 'grabchars.1'
''' Man page for grabchars, uses Larry Wall's "patch" man page as
''' a template.
.de Sh
.br
.ne 5
.PP
\fB\\$1\fR
.PP
..
.de Sp
.if t .sp .5v
.if n .sp
..
'''
'''     Set up \*(-- to give an unbreakable dash;
'''     string Tr holds user defined translation string.
'''     Bell System Logo is used as a dummy character.
'''
.ie n \{\
.tr \(bs-\*(Tr
.ds -- \(bs-
.if (\n(.H=4u)&(1m=24u) .ds -- \(bs\h'-12u'\(bs\h'-12u'-\" diablo 10 pitch
.if (\n(.H=4u)&(1m=20u) .ds -- \(bs\h'-12u'\(bs\h'-8u'-\" diablo 12 pitch
.ds L" ""
.ds R" ""
.ds L' '
.ds R' '
'br\}
.el\{\
.ds -- \(em\|
.tr \*(Tr
.ds L" ``
.ds R" ''
.ds L' `
.ds R' '
'br\}
.TH GRABCHARS 1 LOCAL
.SH NAME
grabchars - get keystrokes directly from user
.SH SYNOPSIS
.B grabchars
[options]
.SH DESCRIPTION
.I Grabchars
gets characters from the user as they are
typed in, without having to wait for the return key to
be pressed.  Among other things, this allows shell scripts
to be written with highly interactive menus.
.PP
By default,
.I grabchars
will obtain one character from stdin, echo that character to stdout,
and return with a status of one; meaning one character read.
.TP 5
.B \-c<valid characters>
Only characters in
.I <valid characters>
are accepted.  All other characters are ignored.
.TP 5
.B \-e
Output goes to
.I stderr
rather than
.I stdout.
.TP 5
.B \-p<prompt>
Sets up a prompt for the user.  See
.I EXAMPLES.
.TP 5
.B \-q<prompt>
Sets up a prompt for the user, except it is printed to
.I stderr
rather than
.I stdout.
.TP 5
.B \-n<number>
Number of characters to read.  By default,
.I grabchars
looks for one character.
.TP 5
.B \-t<seconds>
Time to allow the user to respond.  By default, the user
can take as long as he or she wants to.  The timeout option allows
you to write shell scripts where you can offer some assistance
if it's obvious that the user might be stuck.
.SH EXAMPLES
.TP 5
.B grabchars
gets one keystroke
.TP 5
.B grabchars \-caeiou
get one of the vowels
.TP 5
.B grabchars -c i
get the letter 'i'
.TP 5
.B grabchars '\-penter a letter '
print the prompt "enter a letter "
.TP 5
.B grabchars '\-qenter a letter '
print the prompt ('q' for question) "enter a letter " through
.I stderr.
.TP 5
.B grabchars \-n4
get four characters.
.TP 5
.B grabchars \-t2
timeout after two seconds.
.TP 5
.B grabchars \-n3 \-p 'initials: '
print a prompt and grab three characters.
.TP 5
.B grabchars \-c 0123456789 \-n2 \-t10
get two numbers with a ten second timeout.
.PP
note that arguments like "-n4" or "-n 4" are handled the same way
.SH SEE ALSO
csh(1) and sh(1)
for syntax of
.I csh
and
.I sh
scripts, respectively.
See "The Unix Csh Field Guide", by Gail and Paul Anderson (Prentice Hall),
for an excellent tour of csh and good examples of writing csh scripts.
.SH DIAGNOSTICS
.I
Grabchars
returns
.B \-2
if it times out, or
.B \-1
if it gives a usage statement.  Otherwise, it
returns the number of characters successfully read.
.SH AUTHOR
.nf
Dan Smith (daniel@island.uu.net or {ucbvax!ucbcad,well,sun}!island!daniel)
SHAR_EOF
fi
echo shar: "extracting 'demo'" '(1412 characters)'
if test -f 'demo'
then
       echo shar: "will not over-write existing file 'demo'"
else
cat << \SHAR_EOF > 'demo'
#!/bin/csh -f
clear
cat << GUMBY

	Grabchars demo...

	get one character with "grabchars"
GUMBY
grabchars
echo " status returned was $status"

cat << POKEY

	grab a vowel with "grabchars -caeiou"

	Type something that isn't a vowel at first...

POKEY
grabchars -caeiou
echo " status returned was $status"

cat << WILMA

	prompt the user  with "grabchars -p 'give me any character >> '"
WILMA
grabchars -p 'give me any character >> '
echo " status returned was $status"

cat << FRED

	prompt through stderr  with "grabchars -q 'give me any character >> '",
so that we can set the variable "user_char"...
FRED
set user_char=`grabchars -q 'give me any character >> '`
echo " status returned was $status"
echo '$user_char = '$user_char

cat << BETTY

	enter three characters...  "grabchars -n3"

BETTY
grabchars -n3
echo " status returned was $status"

cat <<  BARNEY

	enter 10 characters within 3 seconds... "grabchars -n10 -t3"


BARNEY
grabchars -n10 -t3
set really_typed=$status
if ($really_typed == 10) then
	echo 'hey\! you got 10\!?'
else
	echo "    $really_typed returned...means that grabchars timed out..."
endif

cat << PEBBLES

	The last one...  get two numbers with a ten second timeout...
	trying "grabchars -c 0123456789 -n2 -t10 -p 'give me 2 numbers >> '

PEBBLES
grabchars -c 0123456789 -n2 -t10 -p 'give me 2 numbers >> '
echo " status returned was $status"
echo ""
echo test/demo done...enjoy\!
SHAR_EOF
chmod +x 'demo'
fi
exit 0
#	End of shell archive