/*
 * 5799-WZQ (C) COPYRIGHT IBM CORPORATION 1986,1987
 * LICENSED MATERIALS - PROPERTY OF IBM
 * REFER TO COPYRIGHT INSTRUCTIONS FORM NUMBER G120-2083
 */
/* $Header:pf.c_ca 12.0$ */
/* $ACIS:pf.c_ca 12.0$ */
/* $Source: /ibm/acis/usr/src/ibm/RCS/pf.c_ca,v $ */

#include <stdio.h>
#include <ctype.h>
#include <sys/ioctl.h>
#include <machinecons/kbd_emul.h>
#include <machinecons/keyboard.h>

char string[KBD_STRING_LENGTH];
char buff[1024];

#define LEFT_BRACE '{'
struct kbdarg arg;
int scan, type;
#define MAX_ARGS 10
#define MAX_LENGTH 128
char args[MAX_ARGS][MAX_LENGTH];
#define MAX_SCAN	256
char *names[MAX_SCAN];		/* names indexed by scan code */
int scan_map[MAX_SCAN];		/* table ordered by appearance */
int meta_map[MAX_SCAN];		/* table ordered by appearance */
int meta_count;			/* count of meta keys */
int scan_count;			/* number of definitions found */
char *getenv();
char *strsave();
char *unquote();
#define MAX_TYPE_NAMES	20
char *typename[MAX_TYPE_NAMES];
char *typenames[MAX_TYPES];
int typenum[MAX_TYPE_NAMES];
int typecnt;
char *ptr;		/* input line pointer */
int debug;
int init;
char *default_file = "/usr/lib/keyboard_codes";
int fncnt;
#define MAX_FNS 128	/* max number of functional names */
char *functions[MAX_FNS];
int safeflag;

main(argc,argv)
char **argv;
{
	register int c, i;
	register FILE *file=0;
	register char *argp;

	for (i=1; i<argc; ++i)
		{
		argp = argv[i];
		if (*argp == '-')
			{
			switch(argp[1])
				{
			case 'f':
				default_file = argv[++i];
				break;
			case 'd':
				++debug;
				break;
			case 'r':		/* reset to standard */
				if (ioctl(0, KBDCRESET, (char *) 0) < 0)
					perror("ioctl KBDCRESET");
				break;
			case 'z':		/* reset string table */
				if (ioctl(0, KBDCRST, (char *) 0) < 0)
					perror("ioctl KBDCRST");
				break;
			case 'S':		/* set standard */
				if (ioctl(0, KBDCSSTD, (char *) 0) < 0)
					perror("ioctl KBDCRESET");
				break;
			case 'e':
				do_cmd(argv[++i]);
				file = stdin;	/* pretend we've had a file */
				break;
				}
			}
		else	
			{
			if ((file = fopen(argp,"r")) == 0)
				{
				perror(argp);
				exit(1);
				}
			else
				{
				do_file(file);
				fclose(file);
				}
			}
		}
	if (file==0)
		do_file(stdin);
	exit(0);
			
}


do_file(file)
register FILE *file;
{
	register int c, i;
	register char *pfx = isatty(fileno(file)) ? "< " : "";

	if (!init)
		scan_init();		/* get the proper table */
	safeflag = *pfx;		/* default to safe mode */
	while (printf(pfx), fgets(buff,sizeof buff,file)) {
		i = strlen(buff);
		if (i > 0 && buff[--i] == '\n')
			buff[i]=0;
		do_cmd(buff);
	}
}

do_cmd(buff)
register char *buff;

{
	register int c, i;

	if (!init)
		scan_init();		/* get the proper table */
	ptr = buff;
	if (buff[0] == '#' || buff[0] == 0)
		return;
	if (prefix("print")) {
		if (prefix("all")) {
			print_all();
			return;
		}
		if (!get_scan_code(&scan,&type))
			return;
		if (!endtest()) {
			printf("bad syntax near==>%.20s\n",ptr);
			return;
		}
		if (debug)
			printf("scan=%d type=%d\n",scan,type);
		arg.kbd_length = KBD_STRING_LENGTH;
		arg.kbd_scan = scan;
		arg.kbd_index = type;
		if (ioctl(0, KBDCGET, &arg) < 0)
			perror("ioctl KBDCGET");
		else {
			print_key(&arg);
			printf("\n");
		}
	}
	else if (prefix("display")) {
		if (prefix("all")) {
			display_all();
			return;
		}
		if (!get_scan_code(&scan,&type))
			return;
		display_key(scan);
	}
	else if (prefix("set")) {
		if (!get_scan_code(&scan,&type))
			return;
		if (!prefix("=")) {
			printf("set = expected near %.20s\n",ptr);
			return;
		}
		if (safeflag && ! checksafe(scan,type))
			return;
		if (debug)
			printf("scan=%d type=%d\n",type,scan);
		if (*ptr == LEFT_BRACE  && get_fn(string)) {
			arg.kbd_length = 1;
			arg.kbd_type = META;
		} else {
			arg.kbd_length = ctlcvt(ptr,string);
			arg.kbd_type = 0;
		}
		if (arg.kbd_length > KBD_STRING_LENGTH)
			arg.kbd_length = KBD_STRING_LENGTH;
		bzero(arg.kbd_text,KBD_STRING_LENGTH);
		bcopy(string, arg.kbd_text,arg.kbd_length);
		arg.kbd_scan = scan;
		arg.kbd_index = type;
		if (ioctl(0, KBDCSET, &arg) < 0)
			perror("ioctl KBDCSET");
	} else if (prefix("reset")) {
		if (ioctl(0, KBDCRESET, (char *) 0) < 0)
			perror("ioctl KBDCRESET");
	} else if (prefix("sstd")) {
		if (ioctl(0, KBDCSSTD, (char *) 0) < 0)
			perror("ioctl KBDCSSTD");
	} else if (prefix("safe")) {
		safe();
	} else if (prefix("help")) {
		help();
	} else if (prefix("click")) {
		click();
	} else if (prefix("space")) {
		space();
	} else if (prefix("end") || prefix("q")) {
		exit(0);
	} else
		printf("unknown command: %.20s\n",buff);
}

help()
{
	register int i;

	printf("Commands available are:\n\n");
	printf("display name	prints the current definitions of name\n");
	printf("display all	prints all current definitions\n");
	printf("print name	prints the current definition of name\n");
	printf("print all	prints all current definitions\n");
	printf("set name=value	defines new value for name\n");
	printf("quit/end	terminates this program\n");
	printf("reset		resets to standard definitions\n");
	printf("sstd		(root only) set standard from working definitions\n");
	printf("safe on/off	sets safe mode (can't change letters etc.)\n");
	printf("\n");
	printf("name is the name on the key top; value can use ^x, \\t, \\33 etc\n");
	printf("name can be prefixed by:");
	for (i=0; i<typecnt-1; ++i)
		printf(" %s",typename[i]);
	printf(" or %s\n",typename[i]);
	printf("e.g. set alt-w=who^m\n");
	printf("\"display all\" will show all names\n");
}

/*
 * read in the scan code definition table.
 * it is read from disk so that it can be easily changed
 * for radically different keyboard layouts (DVORAK).
 * in that case the -f options should be used to provide the
 * appropriate 'default_file'.
 */
int codes = 5;
int codelength = 2;

scan_init()
{
	register char * file;
	register int cnt;
	register FILE *f;
	register int i;
	int line = 0;

	++init;
	file = default_file;	/* use default file */

	if ((f = fopen(file,"r")) == NULL) {
		perror(file);
		exit(1);		/* can't do much without the table */
	}
	while (fgets(buff,sizeof buff,f))
		{
		char *buffp = buff;
		i = strlen(buff);
		if (i > 0 && buff[--i] == '\n')
			buff[i]=0;
		++line;
		if (buff[0] == '*' || buff[0] == '#')
			continue;
		for (i=0,cnt=0; i<MAX_ARGS; ++i)
			if (pscanf(&buffp,"%s",args[i]) != 1)
				break;
		cnt=i;
		while (i<MAX_ARGS)
			args[i++][0] = 0;
/* function name */
		if (strcmp(args[0],"type") == 0) {
			register char *p = strsave(unquote(args[2]));
			int n = atoi(args[1]);
			typename[typecnt] = p;
			typenum[typecnt] = n;
			if (typenames[n] == 0)
				typenames[n] = p;	/* remember first name */
			typecnt++;
		}
		else if (strcmp(args[0],"function") == 0) {
			functions[fncnt++] = strsave(args[1]);
		}
		else if (strcmp(args[0],"codes") == 0) {
			codes=atoi(args[1]);
		}
		else if (strcmp(args[0],"codelength") == 0) {
			codelength=atoi(args[1]);
		}
		else if (isxdigit(args[0][0]) && isxdigit(args[0][1])) {
			register int scan;
			register char *name;
			scan = atox(args[0]);
			if (scan <= 0 || scan >= MAX_SCAN) {
				printf("scan code %x invalid\n",scan);
				continue;
			}
			if (strcmp(args[1],"meta")==0) {
				name = args[3];
				meta_map[meta_count++] = scan;
			}
			else {
				name = args[codes+1];	/* get name field */
				if (*name == 0)
					name = args[1];	/* use normal letter */
				scan_map[scan_count++] = scan;
			}
			if (names[scan])
				printf("scan code %d already set (%s); new value=%s\n",scan,names[scan],name);
			names[scan] = strsave(unquote(name));
		}
		else printf("line %d unrecognized keyword: %s\n",line,buff);
	}
}

pscanf(pstr, fmt, args)
register char **pstr;
char *fmt;
{
	register char *str = *pstr;
	struct _iobuf _strbuf;
	int i;

	_strbuf._flag = _IOREAD|_IOSTRG;
	_strbuf._ptr = _strbuf._base = str;
	_strbuf._cnt = 0;
	while (*str++)
		_strbuf._cnt++;
	i = _doscan(&_strbuf, fmt, &args);
	*pstr = _strbuf._ptr;
	return(i);
}



/*
 * convert string in hex to binary
 */
int atox(ptr)
	register char *ptr;
{
	register int n = 0;
	register int c;

	for (; c = *ptr++;) {
		if (isdigit(c))
			c -= '0';
		else if ('a' <= c && c <= 'f')
			c -= 'a' - 10;
		else if ('A' <= c && c <= 'F')
			c -= 'A' - 10;
		else
			break;
		n = (n << 4) + c;
	}
	return (n);
}



char *strsave(s)
char *s;
{
	char *p, *malloc();
	if ((p = malloc(strlen(s)+1)))
		strcpy(p,s);
	if (debug)
		printf("storing '%s'\n",p);
	return(p);
}

/*
 * locate the scan code and type value from the symbolic description
 * of the key.
 * first look up the prefix codes (typenames)
 * then the rest of the string
 * names are looked up in reverse order of appearance so that 
 * letters are looked for last.
 */
get_scan_code(code,type)
int *code, *type;
{

	register int i;
	register char *p;

	*type = 0;
	*code = 0;
	for (i=0; i<typecnt; ++i)
		if (*(p=typename[i]) && prefix(p)) {
			*type = typenum[i];
			if (debug)
				printf("type=%d ",*type);
			break;
		}
	if (prefix("scan-"))
		{
		i = cvtint(&ptr);
		if (i < 0 || i >= MAX_SCAN)
			{
			printf("invalid scan code %d\n",i);
			return(0);
			}
		if (debug)
			printf(" scan-%d found ",i);
		*code = i;
		return(1);
		}
	for (i=meta_count; --i >= 0; )
		if ((p = names[meta_map[i]]) && prefix(p))
			{
			*code = meta_map[i];
			if (debug)
				printf(" name %d found ",*code);
			return(1);
			}
	for (i=scan_count; --i >= 0; )
		if ((p = names[scan_map[i]]) && prefix(p))
			{
			*code = scan_map[i];
			if (debug)
				printf(" name %d found ",*code);
			return(1);
			}
	printf("Unrecognized scan code %.20s\n",ptr);
	return(0);
}

/*
 * determine if the current string is prefixed by "string"
 * ignoring the case.
 */
prefix(string) char *string;
{
    register char *p = ptr;

	while (isspace(*p))
		++p;		/* ignore white space */    
    while (*string && *p) { register int c = *p;
	if (c == *string || (isupper(c) && tolower(c) == *string)) {
	    ++string;
	     ++p;
	}
	else
	    return(0);
    }
    if (*string)
    	return(0);		/* no match - still some left */
    ptr = p;			/* move the pointer */
    return(1);
}


/*
 * take string "q" and convert the control and escape sequences
 * and store the result into string "p".
 * if ^x is specified store control-x (e.g. 'x'&037
 * if \x is specified store standard value of x (\n, \t, \f, etc)
 * if \n is specified store octal value of n (up to 3 digits)
 * return the number of bytes stored (excluding the terminating null
 * byte). we return this so that the caller (if required) can distringuish
 * between a \0 the user specifies and the actual end of string.
 * note that it is ok if p == q as the strlen(p) <= strlen(q)
 */

int ctlcvt(q,p) register char *p, *q;
{
register int c;
char *s = p;

for (; *p = *q++; ++p)
	{
	if (*p == '^')
		*p = *q++ & 037;	/* control character */
	else if (*p == '\\')
		{
		switch(c = *q++)
			{
		case 'f':
			c = '\f';
			break;
		case '\\':
			c = '\\';
			break;
		case 'r':
			c = '\r';
			break;
		case 'n':
			c = '\n';
			break;
		case 't':
			c = '\t';
			break;
		case 'b':
			c = '\b';
			break;
		default:
			if (isdigit(c))
				{
				register int n = 1;
				
				c -= '0';
				while (isdigit(*q) && ++n <= 3)
					c = c * 8 + *q++ - '0';
				}
			}
		*p = c;
		}
	}
return(p-s);
}

/* convert str to int like atoi, but return updated string ptr
 * i.e. point to first nondigit char
 */

int cvtint(ptr) char **ptr;
{
register char *p;
register int n;

for (p = *ptr, n = 0; isdigit(*p); )
	n = n * 10 + *p++ - '0';
*ptr = p;
return(n);
}

/*
 * remove leading trailing quotes */
char *unquote(name)
register char *name;
{
	register char *p=name, *q;
	if (*name == '"') {
		for (q = name+1;  *p = *q++; )
			++p;
		if (p[-1] == '"')
			p[-1] = 0;
	}
	return(name);
}

endtest()
{
(void) prefix("");
return(*ptr == 0);
}

print_key(arg)
register struct kbdarg *arg;
{
	register int i, c;
	register int scan = arg->kbd_scan;
	register int type = arg->kbd_index;
	register int length = arg->kbd_length;
	if (debug)
		printf("scan=%d, type=%d, len=%d ",
			scan,type,length);
	if (names[scan])
		printf("%s%s=",typenames[type],names[scan]);
	else
		printf("%sscan-%d=",typenames[type],scan);
	for (i=0; i<length; ++i)
		{
		c = arg->kbd_text[i];
		if (i!=0 && c == 0)
			break;
		if (arg->kbd_type & META)
			{
			c -= META;
			if (functions[c])
				printf("{%s}",functions[c]);
			else
				printf("{META%d}",c);
			}
		else if (c < 040)
			printf("^%c",c + '@');
		else if (c == ' ' || c >= 0177)
			printf("\\%o",c);
		else
			printf("%c",c);
		}
}

print_all()
{
register int scan, type;

for (scan=0; scan<MAX_SCAN; ++scan)
	for (type=0; type<MAX_TYPES; ++type) {
			arg.kbd_length = KBD_STRING_LENGTH;
			arg.kbd_scan = scan;
			arg.kbd_index = type;
			if (ioctl(0, KBDCGET, &arg) < 0)
				break;
			else
				if (!((arg.kbd_type & META) && 
					(arg.kbd_text[1] == FN_IGNORE))) {
					print_key(&arg);
					printf("\n");
				}
	}
}

get_fn(p)
register char *p;
{

	register int i;
	register char *old_ptr = ptr;

	if (prefix("{"))
		for (i=0; i<fncnt; ++i) {
			if (prefix(functions[i]) && prefix("}")) {
				p[0] = META+i;
				return(1);
			}
		}
	ptr = old_ptr;
	return(0);
}

display_all()
{
register int i, scan;

	for (i=0; i<scan_count; ++i) {
		scan = scan_map[i];
		display_key(scan);
	}
}

display_key(scan)
register int scan;
{
	register int i, type;
	register int flag;

	flag = 0;
	for (type=0; type<MAX_TYPES; ++type) {
		arg.kbd_length = KBD_STRING_LENGTH;
		arg.kbd_scan = scan;
		arg.kbd_index = type;
		if (ioctl(0, KBDCGET, &arg) < 0)
			break;
		else
			if (!((arg.kbd_type & META) && 
					(arg.kbd_text[1] == FN_IGNORE))) {
				print_key(&arg);
				printf("	");
				++flag;
			}
	}
	if (flag)
		printf("\n");
}

safe()
{
yesno(&safeflag);
}

yesno(flag)
int *flag;
{
	if (prefix("yes") || prefix("on"))
		*flag = 1;
	else if (prefix("no") || prefix("off"))
		*flag = 0;
	else {
		printf("yes or no expected (%s found)\n",ptr);
		return(0);
	}
	return(1);
}

/*
 * a safe name is one that is not a normal letter 
 * we do this so that the user cannot easily change, for example, 
 * the definition of 'a'.
 */
checksafe(scan,type)
register int scan, type;
{
	register char *name = names[scan];

	if (name == 0 || type > 1)
		return(1);		/* undefined or not normal cases */
	if (!isalpha(name[0]))
		return(1);
	if (!(name[1] == 0 || strcmp(name,"space") == 0
			|| strcmp(name,"enter") == 0))
		return(1);
	printf("That might be unwise; do 'safe off' to override safefy check\n");
	return(0);
}

space()
{
	int n = 0;
	if (ioctl(0, KBDSGET, &n) == -1)
		perror("ioctl KBDSGET");
	else
		printf("space=%d\n",n);
}

click()
{
	register char *p;
	int n = 0;

	if (prefix("?")) {
		if (ioctl(0, KBDGCLICK, &n) == -1)
			perror("ioctl KBDGCLICK");
		else {
			switch(n)
				{
			case CLICK_OFF:
				p = "off";
				break;
			case CLICK_SOFT:
				p = "software";
				break;
			case CLICK_HARD:
				p = "hardware";
				break;
			case CLICK_BOTH:
				p = "both";
				break;
			default:
				p = "strange";
				break;
				}
			printf("click was %s\n",p);
		}
		return;
	}

	if (prefix("soft"))
		n = CLICK_SOFT;
	else if (prefix("both"))
		n = CLICK_BOTH;
	else if (prefix("hard"))
		n = CLICK_HARD;
	else
		if (!yesno(&n))
			return;
	if (ioctl(0, KBDSCLICK, &n) == -1)
		perror("ioctl KBDSCLICK");
}
