v13i063: Generic user interface kit, Part01/02

Rich Salz rsalz at bbn.com
Wed Feb 24 00:31:06 AEST 1988


Submitted-by: Marc Majka <majka at ubc.csnet>
Posting-number: Volume 13, Issue 63
Archive-name: iface/part01

[  This uses BSD tty driver, and will take a bit of grundge to port it
   to SystemV, let alone Version7.  --r$ ]

This package contains a "generic" user interface which may be useful.
It provides word completion, CBREAK I/O, and keybinding.  It is
reasonably easy to tailor to an application.  The distributed
version compiles and runs, and contains a couple of functions
which demonstrate the flavour of the interface.  Cut on the line
and feed to "sh". It will create the directory "iface" and put
everything in there.

Manifest:
comc.c   - word completion routine
exec.c   - executive-type functions
iface.h  - header stuff
fns.c    - example functions to demo iface
init.c   - initialization routines
io.c     - input/output routines
iface.c  - main routine
Makefile - to compile it
README   - describes what it is and how it works

---
Marc Majka


- - - CUT - - - CUT - - - CUT - - - CUT - - - CUT - - - CUT - - -
#! /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 the files:
#    iface
export PATH; PATH=/bin:$PATH
if test ! -d 'iface'
then
	mkdir 'iface'
fi
cd 'iface'
if test -f 'comc.c'
then
	echo shar: will not over-write existing file "'comc.c'"
else
cat << \SHAR_EOF > 'comc.c'
/*
	getcom: get a command (character string) from standard input
	getcom takes a list of character strings in the same format
	as that used for argv, and a count of the number of strings
	in the list, as in argc.  It does EMACS-style string
	completion.  Getcom returns as its value, the index of the
	command.  -1 is returned if the user aborts the completion.
	Note that the command list "clist" must contain its strings
	in sorted order.  Also note that getcom expects to have the
	terminal in raw or cbreak mode, with no character echoing.

	Characters are read and appended to a command string "cmd".
	Certain characters have special meanings:

	? causes getcom to print all possible completions
	^G and ESC abort and return -1
	<space>, <tab>, and <return> cause getcom to complete
	<linefeed> causes completion to the first possible string
	<backspace> has the expected result
	<DEL> is treated at <backspace>
	^U clears the cmd string and restarts completion

*/
#include <stdio.h>

getcom(clist,ncmd,prompt)
char **clist;
int ncmd;
char *prompt;
{
	int cn,first,last,match,endpt,start,i,c;
	char *entry,cmd[256];

	match = endpt = start = first = 0;
	last = ncmd - 1;
	cmd[0] = '\0';

	while (!match) {
		c = getchar();

		switch(c) {

			case '?':
				match = try_match(&first,&last,&start,&endpt,&cn,clist,cmd);
				if (!match) {
					printf("\b \b\n\n");
					helpcom(clist,first,last);
					printf("\n%s %s",prompt,cmd);
				}
				else {
					entry = clist[cn];
					for (i = endpt; entry[i] != '\0'; i++)
						printf("%c",entry[i]);
				}
				break;

			case '\07': /* ^G */
			case '\033': /* ESC */
			case '\04': /* ^D */
				return(-1);

			case ' ': /* blank */
			case '\t':  /* tab */
			case '\r': /* return */
				match = try_match(&first,&last,&start,&endpt,&cn,clist,cmd);
				if (match) {
					entry = clist[cn];
					for (i = endpt; entry[i] != '\0'; i++)
						printf("%c",entry[i]);
				}
/*				else printf("%c",7); */
				break;

			case '\n': /* newline */
				match = try_match(&first,&last,&start,&endpt,&cn,clist,cmd);
				if (!strcmp(clist[first],cmd)) {
					cn = first;
					match = 1;
				}
				if (!match) printf("%c",7);
				break;

			case '\b': /* backspace */
			case 127:	/* DEL */
				if (endpt > 0) {
					printf("\b \b");
					endpt--;
					cmd[endpt] = '\0';
				}
				start = first = 0;
				last = ncmd - 1;
				break;

			case '\025':  /* ^U */
				for (i = 0; i < endpt; i++) printf("\b \b");
				match = endpt = start = first = 0;
				last = ncmd - 1;
				cmd[0] = '\0';
				break;

			default: /* anything else */
				printf("%c",c);
				cmd[endpt++] = c;
				cmd[endpt] = '\0';
				break;
		}
	}
	return(cn);
}

try_match(f,l,s,p,n,clist,cmd)
int *f,*l,*s,*p,*n;
char **clist,*cmd;
{
	int i,k,tf,tl;
	char *fent,*lent;

	if (*p == 0) return(0);
	tf = *f;
	tl = *l;
	k = *p + 1;
	fent = clist[tf];
	lent = clist[tl];

	for (i = *s + 1; i < k; i++) {
		fent = clist[tf];
		while ((tf < tl) && (strncmp(cmd,fent,i) > 0)) fent = clist[++tf];
		lent = clist[tl];
		while ((tl > tf) && (strncmp(cmd,lent,i) < 0)) lent = clist[--tl];
	}

	if (tf == tl) {
		if (strncmp(cmd,fent,*p) == 0) {
			*f = tf; *l = tl; *s = *p; *n = tf;
			return(1);
		}
		else {
			i = *s;
			while ((i < *p) && (cmd[i] == fent[i])) i++;
			cmd[i] = '\0';
			k = i;
			for (; i < *p; i++) printf("\b \b");
			*p = k;
			return(0);
		}
	}
	else {
		i = *p;
		while (fent[i] == lent[i]) {
			cmd[i] = fent[i];
			printf("%c",cmd[i++]);
		}
		cmd[i] = '\0';
		*f = tf; *l = tl; *p = i; *s = *p;
		return(0);
	}
}

helpcom(w,f,l)
char **w;
int f,l;
{
	int max, i, len, n;

	max = 0;
	for (i = f; i <= l; i++) {
		len = strlen(w[i]);
		if (len > max) max = len;
	}
	n = (l - f) + 1;

	printf("? choose one of the following\n");
	printlist(max,n,w+f);
	printf("\n\n");
	printf("? ? for command list\n");
	printf("? ^D, ^G or ESC to exit, ^U to clear\n");
	printf("? <space>, <tab> or <return> to complete\n");
	printf("? <linefeed> to insist on first choice\n");
}

printlist(size,items,list)
int size,items;
char *list[];
{
	int rows, cols, i, j, x, y, len, n;
	char blank[256];

	for (i = 0; i < 256; i++) blank[i] = ' ';

	size += 1;

	cols = 79 / size;
	rows = items / cols;
	if (items > (rows * cols)) rows++;
	cols = items / rows;
	if (items > (rows * cols)) cols++;

	n = rows * cols;
	y = -1;
	x = 0;

	for (i = 0; i < n; i++) {
		if (!(i % cols)) {
			printf("\n");
			y++;
			x = y;
		}
		if (x < items) {
			len = strlen(list[x]);
			j = (size - len) + 1;
			blank[j] = '\0';
			printf("%s%s",list[x],blank);
			blank[j] = ' ';
		}
		x += rows;
	}
}

SHAR_EOF
fi # end of overwriting check
if test -f 'exec.c'
then
	echo shar: will not over-write existing file "'exec.c'"
else
cat << \SHAR_EOF > 'exec.c'
#include "iface.h"

print_version()
{
	int ref;

	ref = sym_ref("version");
	printf("version %s\n",symbol[ref].strval);
}

exec(ref)
int ref;
{
	int (*comptr)();

	comptr = cptr[symbol[ref].value];
	(*comptr)();
}

ESC_prefix()
{
	int ref;

	lastkey = getchar();
	ref = binding[lastkey][1];
	exec(ref);
}

CTL_X_prefix()
{
	int ref;

	lastkey = getchar();
	ref = binding[lastkey][2];
	exec(ref);
}

CTL_Y_prefix()
{
	int ref;

	lastkey = getchar();
	ref = binding[lastkey][3];
	exec(ref);
}

BOGUS()
{
	putchar('\07');
}

quit()
{
	oldterm();
	exit(0);
}

oldterm()
{
	ioctl(fileno(stdin),TIOCGETP,&iobasic);
	iobasic.sg_flags = oldflags;
	ioctl(fileno(stdin),TIOCSETP,&iobasic);

	ioctl(fileno(stdin),TIOCGLTC,&termc);
	termc.t_suspc = 26;
	termc.t_dsuspc = 25;
	ioctl(fileno(stdin),TIOCSLTC,&termc);
}

newterm()
{
	ioctl(fileno(stdin),TIOCGETP,&iobasic);
	oldflags = iobasic.sg_flags;
	iobasic.sg_flags |= CBREAK;
	iobasic.sg_flags &= ~ECHO;
	ioctl(fileno(stdin),TIOCSETN,&iobasic);

	ioctl(fileno(stdin),TIOCGLTC,&termc);
	termc.t_suspc = -1;
	termc.t_dsuspc = -1;
	ioctl(fileno(stdin),TIOCSLTC,&termc);
}

extended_command()
{
	int cn, ref;

	printf(": ");
	cn = getcom(cmds,ncmds,":");
	printf("\n");
	ref = sym_ref(cmds[cn]);
	exec(ref);
}

sym_ref(name)
char *name;
{
	int ref;

	if (name == NULL) return(0);

	for (ref = 0;
		 symbol[ref].name != NULL && strcmp(name,symbol[ref].name);
		 ref++);

	if (symbol[ref].name != NULL) return(ref);
	return(0);
}

describe_key()
{
	char key;
	int tab, ref;

	printf("describe-key: ");
	key = getchar();

	switch (key) {
		case 27: printf("ESC-");   tab = 1; break;
		case 24: printf("CTL-X-"); tab = 2; break;
		case 25: printf("CTL-Y-"); tab = 3; break;
		default: put_char(key);	tab = 0; break;
	}

	if (tab) {
		key = getchar();
		put_char(key);
	}

	ref = binding[key][tab];
	printf(" is bound to the command \"%s\"\n",symbol[ref].name);
}

bind_key()
{
	char key;
	int tab, ref, cn;

	printf("bind-key: ");
	key = getchar();

	switch (key) {
		case 27: printf("ESC-");   tab = 1; break;
		case 24: printf("CTL-X-"); tab = 2; break;
		case 25: printf("CTL-Y-"); tab = 3; break;
		default: put_char(key);	tab = 0; break;
	}

	if (tab) {
		key = getchar();
		put_char(key);
	}

	printf(" to command: ");
	cn = getcom(cmds,ncmds,"bind-key to command:");
	printf("\n");
	ref = sym_ref(cmds[cn]);
	binding[key][tab] = ref;
}

describe_bindings()
{
	int i, k, x, max;

	max = 0;
	for (i = 1; i < ncmds; i++) {
		x = strlen(cmds[i]);
		if (x > max) max = x;
	}

	for (i = 1; i < ncmds; i++) {
		x = sym_ref(cmds[i]);
		printf("%s",cmds[i]);
		for (k = strlen(cmds[i]); k < max; k++) printf(" ");
		printf("  ");

		for (k = 0; k < 128; k++)
			if (binding[k][0] == x) {
				printf(" (");
				put_char(k);
				printf(")");
			}
		for (k = 0; k < 128; k++)
			if (binding[k][1] == x) {
				printf(" (ESC-");
				put_char(k);
				printf(")");
			}
		for (k = 0; k < 128; k++)
			if (binding[k][2] == x) {
				printf(" (CTL-X-");
				put_char(k);
				printf(")");
			}
		for (k = 0; k < 128; k++)
			if (binding[k][3] == x) {
				printf(" (CTL-Y-");
				put_char(k);
				printf(")");
			}
		printf("\n");
	}
}

dump_symbol_table()
{
	int i;

	for (i = 0; i <= nsyms; i++) {
		printf("%4d: %s ",i,symbol[i].name);
		switch (symbol[i].type) {
			case COMMAND:  printf("[command] "); break;
			case VARIABLE: printf("[variable] "); break;
			default: printf("[%d] ",symbol[i].type);
		}
		printf("\"%s\" %d\n",symbol[i].strval,symbol[i].value);
	}
}
SHAR_EOF
fi # end of overwriting check
if test -f 'iface.h'
then
	echo shar: will not over-write existing file "'iface.h'"
else
cat << \SHAR_EOF > 'iface.h'
#include <setjmp.h>
#include <sgtty.h>
#include <signal.h>
#include <stdio.h>
#define NSYM 256
#define COMMAND 0
#define VARIABLE 1
extern char *cmds[256], *syms[NSYM], lastkey;
extern int (*cptr[256])(), binding[128][4];
extern int oldflags, ncmds, nsyms;

extern struct ltchars termc;
extern struct sgttyb iobasic;
jmp_buf topenv;
void sigtrap();
void addcom();
char *strcpy();

struct sym_struct {
	char *name,*strval;
	int type,value;
};
extern struct sym_struct symbol[NSYM];
SHAR_EOF
fi # end of overwriting check
if test -f 'fns.c'
then
	echo shar: will not over-write existing file "'fns.c'"
else
cat << \SHAR_EOF > 'fns.c'
#include "iface.h"

do_something()
{
	long random();
	int i;
	char name[128];

	i = (int)(random() % 11);

	switch (i) {
		case  0: printf("Hello World\n"); break;
		case  1: printf("This space intentionally left blank\n"); break;
		case  2: printf("***REPLACE THIS LINE WITH YOUR MESSAGE***\n"); break;
		case  3: printf("Testing 1, 2, 3, 4\n"); break;
		case  4: printf("Please ignore this message\n"); break;
		case  5: printf("Error\07\n"); break;
		case  6: printf("version 0.0\n"); break;
		case  7: printf("What the hell is a Mimsey Tove?\n"); break;
		case  8: printf("(core not dumped)\n"); break;
		case  9: printf("[ynq] "); put_char(getchar()); printf("\n"); break;
		case 10:
			printf("Please enter your name: ");
			get_string(name);
			printf("\nThank you\n");
			break;
	}
}

do_nothing() {printf("Done!\n");}
SHAR_EOF
fi # end of overwriting check
if test -f 'init.c'
then
	echo shar: will not over-write existing file "'init.c'"
else
cat << \SHAR_EOF > 'init.c'
#include "iface.h"

init()
{
	srandom(getpid());
	signal(SIGINT,sigtrap);
	signal(SIGHUP,sigtrap);
	newterm();
	init_symbols();
	init_variables();
	init_bindings();
}

init_symbols()
{
	int i,j;
	char *malloc();
	int (*command_ptr())();

	for (i = 0; i < 128; i++)
		for (j = 0; j < 4; j++) binding[i][j] = 0;

	nsyms = 0;
	cmds[0] = malloc(6);
	strcpy(cmds[0],"BOGUS");
	cptr[0] = command_ptr("BOGUS");
	symbol[0].name = cmds[0];
	symbol[0].value = 0;
	ncmds = 1;

	addcom("CTL-X-prefix");
	addcom("CTL-Y-prefix");
	addcom("ESC-prefix");
	addcom("bind-key");
	addcom("describe-bindings");
	addcom("describe-key");
	addcom("do-nothing");
	addcom("do-something");
	addcom("dump-symbol-table");
	addcom("extended-command");
	addcom("print-version");
	addcom("quit");
}

init_variables()
{
	addvar("dummy","",0);
	addvar("version","0.0",0);
}

void addcom(str)
char *str;
{
	char *malloc();
	int ref;

	if (strcmp(str,cmds[ncmds-1]) < 0) {
		printf("# Yuck! initial command %s is out of place!\n",str);
		quit();
	}

	cmds[ncmds] = malloc((unsigned)(strlen(str)+1));
	strcpy(cmds[ncmds],str);
	cptr[ncmds] = command_ptr(str);
	ncmds++;

	for (ref = 1; (ref < NSYM) && (symbol[ref].name != NULL); ref++);
	symbol[ref].name = cmds[ncmds-1];
	symbol[ref].type = COMMAND;
	symbol[ref].value = ncmds-1;
	addsym(symbol[ref].name);
}

addvar(str,strval,val)
char *str, *strval;
int val;
{
	char *malloc();
	int ref;

	for (ref = 1; (ref < NSYM) && (symbol[ref].name != NULL); ref++);
	symbol[ref].name = malloc(strlen(str) + 1);
	strcpy(symbol[ref].name,str);
	symbol[ref].strval = malloc(strlen(strval) + 1);
	strcpy(symbol[ref].strval,strval);
	symbol[ref].type = VARIABLE;
	symbol[ref].value = val;
	addsym(str);
}

int (*command_ptr(str))()
char *str;
{
	int BOGUS(), CTL_X_prefix(), CTL_Y_prefix(), ESC_prefix();
	int quit(), extended_command(), dump_symbol_table();
	int describe_bindings(), describe_key(), bind_key(), print_version();
	int do_something(), do_nothing();

	if (!strcmp(str,"BOGUS")) return(BOGUS);
	if (!strcmp(str,"CTL-X-prefix")) return(CTL_X_prefix);
	if (!strcmp(str,"CTL-Y-prefix")) return(CTL_Y_prefix);
	if (!strcmp(str,"ESC-prefix")) return(ESC_prefix);
	if (!strcmp(str,"bind-key")) return(bind_key);
	if (!strcmp(str,"describe-bindings")) return(describe_bindings);
	if (!strcmp(str,"describe-key")) return(describe_key);
	if (!strcmp(str,"do-nothing")) return(do_nothing);
	if (!strcmp(str,"do-something")) return(do_something);
	if (!strcmp(str,"dump-symbol-table")) return(dump_symbol_table);
	if (!strcmp(str,"extended-command")) return(extended_command);
	if (!strcmp(str,"print-version")) return(print_version);
	if (!strcmp(str,"quit")) return(quit);
	return(BOGUS);
}

addsym(str)
char *str;
{
	int i, j, scan;
	char *malloc();

	i = 0;
	scan = 1;

	while ((scan) && (i < nsyms))
		if (strcmp(str,syms[i]) < 0) scan = 0;
		else i++;

	for (j = nsyms; j > i; j--) syms[j] = syms[j-1];

	syms[i] = malloc(strlen(str) + 1);
	strcpy(syms[i],str);
	nsyms++;
}

init_bindings()
{
	binding[' '][0] = sym_ref("do-something");
	binding['d'][0] = sym_ref("do-something");
	binding['n'][0] = sym_ref("do-nothing");
	binding['q'][0] = sym_ref("quit");
	binding['x'][0] = sym_ref("extended-command");
	binding['x'][1] = sym_ref("extended-command");
	binding[24] [0] = sym_ref("CTL-X-prefix");
	binding[25] [0] = sym_ref("CTL-Y-prefix");
	binding[27] [0] = sym_ref("ESC-prefix");
	binding[27] [1] = sym_ref("quit");
}
SHAR_EOF
fi # end of overwriting check
if test -f 'io.c'
then
	echo shar: will not over-write existing file "'io.c'"
else
cat << \SHAR_EOF > 'io.c'
#include <stdio.h>

get_string(str)
char *str;
{
	char c;
	int i;

	i = 0;
	str[i] = '\0';

	while ('\n' != (c = getchar())) {
		switch (c) {
		case 8: /* BACKSPACE */
			if (i > 0) {
				if (str[i-1] < 32 || str[i-1] == 127) printf("\b \b");
				str[--i] = '\0';
				printf("\b \b");
				fflush(stdout);
			}
			break;
		case 127: /* DEL */
			for (i--; i > 0; i--) {
				if (str[i-1] < 32 || str[i-1] == 127) printf("\b \b");
				printf("\b \b");
			}
			str[0] = '\0';
			fflush(stdout);
			break;
		default:
			put_char(c);
			str[i++] = c;
			break;
		}
	}
	str[i] = '\0';
}

put_char(c)
char c;
{
	if (c > 31) {
		if (c == 127) printf("^?");
		else printf("%c",c);
	}
	else printf("^%c",c+64);
}
SHAR_EOF
fi # end of overwriting check
if test -f 'iface.c'
then
	echo shar: will not over-write existing file "'iface.c'"
else
cat << \SHAR_EOF > 'iface.c'
#include <setjmp.h>
#include <sgtty.h>
#include <signal.h>
#include <stdio.h>

#define NSYM 256
#define forever for(;;)

char *cmds[256], *syms[NSYM], lastkey;
int (*cptr[256])(), binding[128][4];
int oldflags, ncmds, nsyms;

struct ltchars termc;
struct sgttyb iobasic;
jmp_buf topenv;
void sigtrap();

struct sym_struct {
	char *name,*strval;
	int type,value;
};
struct sym_struct symbol[NSYM];


main()
{
	printf("Welcome to interface\n");
	init();
	print_version();
	user_level();
	exit(0);
}

user_level()
{
	int ref;

	forever {
		if (setjmp(topenv)) printf("top level\n");
		lastkey = getchar();
		ref = binding[lastkey][0];
		exec(ref);
	}
}

void sigtrap(sig)
int sig;
{
	switch(sig) {
		case SIGINT:
			printf("\nInterrupt\n");
			longjmp(topenv,1);
		case SIGHUP:
			quit();
	}
}
SHAR_EOF
fi # end of overwriting check
if test -f 'Makefile'
then
	echo shar: will not over-write existing file "'Makefile'"
else
cat << \SHAR_EOF > 'Makefile'
CFLAGS = -O
BIN=iface

iface: iface.o init.o fns.o exec.o iface.h comc.o io.o
	cc -g -o ${BIN} iface.o init.o fns.o exec.o comc.o io.o

lint:
	lint iface.c init.c fns.c exec.c iface.h comc.c io.c
SHAR_EOF
fi # end of overwriting check
if test -f 'README'
then
	echo shar: will not over-write existing file "'README'"
else
cat << \SHAR_EOF > 'README'
This directory contains the program iface.  Iface is a generic user interface,
which supports:

- CBREAK mode terminal I/O
- Keybindings for invoking top-level functions
- extended-command function invocation
- Name completion


Internally, iface has the following structures:

- 4 keybinding tables for regular keys and ESC, CTL-X and CTL-Y prefix keys
- A symbol table for command references


The interface can be easily extended to any application by adding new functions
in the file fns.c, adding the names to the symbol table in init_symbols in the
file init.c, and adding a test to the name-to-function-pointer routine called
command_ptr, in the file init.c.  Key bindings may be made in init_bindings.


Several top-level functions exist.  They are:

BOGUS:  prints a BELL character (^G or \07) causing the terminal to feep.
	generally used as a binding for otherwise unbound keys, or as an
	error function.

CTL-X-prefix: switches to the CTL-X keymap and "executes" the next character.

CTL-Y-prefix: switches to the CTL-Y keymap and "executes" the next character.

ESC-prefix: switches to the ESC keymap and "executes" the next character.

bind-key: changes an entry in a keybinding table.

describe-bindings: prints a list of command names and associaled keybindings

describe-key: prints the command name associated with a key.

extended-command: allows command invocation by name.

quit: resets the terminal and exits.


Two commands called "do-something" and "do-nothing" have been included in
this distribution.  They are just there to demonstrate how new commands may
be added.


This interface has proven useful in developing several programs at UBC.  Have
fun with it and feel free to add or change anything.  It is all public domain.
Please leave my name attached.  Please don't sell it for profit.  Comments and
suggestions are welcome.

---
Marc Majka  -  UBC Laboratory for Computational Vision

CDN:  majka at vision.ubc.cdn
UUCP: majka at ubc-vision.uucp
SHAR_EOF
fi # end of overwriting check
cd ..
# End of shell archive
exit 0

-- 
For comp.sources.unix stuff, mail to sources at uunet.uu.net.



More information about the Comp.sources.unix mailing list