[comp.sys.att] Keyboard remapper for Unix PC at Last!

ford@kenobi.UUCP (Mike Ditto) (10/26/87)

Well, thanks to Paul Fox at AT&T for pointing me in the right direction (or
at the right include files).  I have used the information in his posting
(which was paraphrased from <sys/kbd.h>) to write the much-needed keyboard
re-mapping program for the Unix PC.  The command is called 'keyfix' and it
lets you examine or modify the definition of any code-generating key on the
console keyboard (it will not affect the shift, control, or lock keys them-
selves).

The usage is: keyfix <keynum> [ <norm> <shift> <ctrl> <flags> ]

All arguments are numbers which are decimal unless preceded by 0 for octal
	or 0x for hexadecimal.  If only <keynum> is given then the current
	definition of that keycode is listed but not changed.

keynum:	The hardware keycode of the key you are interested in.  You can
	find these listed in the comments in /usr/include/sys/kbd.h.

norm:	The ascii code this key should generate if pressed alone.

shift:	The ascii code this key should generate if pressed with SHIFT held.

control:The ascii code this key should generate if pressed with CONTROL held.

flags:	The flags for this keycode, as defined in <sys/kbd.h>.


Note that the 'ascii codes' are 16-bit numbers.  The low-order byte (last two
digits) are the actual code.  If the high-order byte is non-zero, it is used
as an index into the kprefix[] table which you can see in <sys/kbd.h>.  This
is how one key (like HOME) can generate a multi-byte sequence (like ^[[H).


For example, suppose you want what is now the 'BREAK/RESET' key to be RUBOUT
(a.k.a. DEL) instead.  You grep for BREAK in /usr/include/sys/kbd.h and see
that it is keycode 0x25.  So you type "keyfix 0x25" to see what it's current
setting is:

	$ keyfix 0x25
	Old keymap[0x25] = 0x00ff,0x00ff,0x00ff, 0
	$ 

You see that it is currently set to generate 0x00ff regardless of whether
shift or control are pressed.  Apparrently, 0xff is what 'BREAK/RESET'
returns by default.  Now you want it to be RUBOUT, again regardless of the
state of shift and control.  So you type:

	$ keyfix 0x25 0x7f 0x7f 0x7f 1
	Old keymap[0x25] = 0x00ff,0x00ff,0x00ff, 0
	New keymap[0x25] = 0x007f,0x007f,0x007f, 1
	$ 

Notice that the <flags> argument (the last one) was given as 1 instead of
0, which it had been before.  In <sys/kbd.h> REPT is defined as 0x1, meaning
that if that bit (bit 0, 0x0001) is set in the flags then the corresponding
key will repeat if held down.  The other flags, CAPLCK (0x2) and NUMLCK (0x4)
determine whether the 'lock' keys will affect them.  You can add any or all
of the above flags together and use the result as the <flags> argument to
keyfix.

Here are the keyfixes that I recommend that everyone use:

	keyfix 0x36 0x36 0x5e 0x1e 1	# Make control-^ work
	keyfix 0x2d 0x2d 0x5f 0x1f 1	# Make control-_ work
	keyfix 0x20 0x20 0x20 0x00 1	# Make control-space be a NUL
	keyfix 0x25 0x7f 0x7f 0x7f 1	# Make BREAK/RESET be RUBOUT
	keyfix 0x1b 0x1b 0x7f 0xff 1	# Make control-escape be BREAK/RESET
					#  (just in case it is ever needed)

Note that you must be super-user in order to run keyfix.  This is because
it writes to internal data structures in the kernel, and because it has a
global effect on the system (its effects last until the machine is booted).
I suggest that your favorite keyfix commands be run after each reboot, perhaps
in /etc/rc, or in a file in the /etc/daemons directory.  If you really think
you want to be running it all the time, you could make it set-uid to root.

There is one peculiarity in the process of changing these keymaps.  If you
have CAPCTRL then you have a complete replacement for the normal keyboard
driver and its tables.  Therefore, modifying the 'keymap' table in the kernel
has no effect.  Don't worry, though.  keyfix is smart enough to check if you
have the CAPCTRL driver in the /etc/lddrv directory and use its table if it
is there.  Otherwise the /unix table is used.  The only real problem with
this is that it is possible for the driver to exist in /etc/lddrv but not be
loaded into the kernel.  This will only happen if you PARTIALLY de-installed
CAPCTRL by some non-standard method (like typing "lddrv -d kbd").  If you
have installed CAPCTRL and then removed it, make sure you remove or rename
"/etc/lddrv/kbd".

Thanks again to Paul Fox for contributing to this newsgroup.  Now if he
could only get me information (source?) for changing the extra control key
into a meta-key.  Then I could actually start doing work on the console
instead of from the Amiga on tty000.

Well, here's the source to keyfix...
If somebody needs executables let me know.

					-=] Ford [=-

"GNU does not eliminate			(In Real Life:  Michael Ditto)
all the world's problems,		ford%kenobi@crash.CTS.COM
only some of them." -rms		...!crash!kenobi!ford

-------------------------------- cut here --------------------------------
/************************************************************
 *
 * This program was written by me, Mike "Ford" Ditto, and
 * I hereby release it into the public domain in the interest
 * of promoting the development of free, quality software
 * for the hackers and users of the world.
 *
 * Feel free to use, copy, modify, improve, and redistribute
 * this program, but keep in mind the spirit of this
 * contribution; always provide source, and always allow
 * free redistribution (shareware is fine with me).  If
 * you use a significant part of this code in a program of
 * yours, I would appreciate being given the appropriate
 * amount of credit.
 *				-=] Ford [=-
 *
 ************************************************************/

#include <stdio.h>
#include <fcntl.h>
#include <ctype.h>
#include <errno.h>
#include <pwd.h>
#include <grp.h>
#include <sys/types.h>
#include <sys/kbd.h>
#include <nlist.h>


extern long lseek(), strtol();
extern void perror(), exit();


void kcopy(), kwrite();

char *progname;

#define ldkeymapaddr (myldsyms[0].n_value)

struct nlist myldsyms[] =
{
    { "ldkeymap", },
    { (char *)0, },
};

#define keymapaddr (mysyms[0].n_value)

struct nlist mysyms[] =
{
    { "keymap", },
    { (char *)0, },
};

char buf[BUFSIZ];

int kmem;
int keynum, norm, shift, ctrl, flags;


void usage()
{
    fprintf(stderr, "usage: %s <keynum> [ <norm> <shift> <ctrl> <flags> ]\n",
	    progname);
    fprintf(stderr, "	Numbers are decimal unless preceded by 0 or 0x\n");
    exit(-1);
}


main(argc, argv)
int argc;
char *argv[];
{

    progname = *argv;

    setup();

    if (argc != 2 && argc != 6)
	usage();

    keynum = numcvt(argv[1]);
    if (keynum<0 || keynum>=0x80)
    {
	fprintf(stderr, "%s: invalid keynum 0x%02x\n", progname, keynum);
	exit(-2);
    }

    if (argc>2)
    {
	norm = numcvt(argv[2]);
	shift = numcvt(argv[3]);
	ctrl = numcvt(argv[4]);
	flags = numcvt(argv[5]);
    }

    return keyfix();
}


int numcvt(str)
char *str;
{
    long value;
    char *ptr;

    value = strtol(str, &ptr, 0);

    if (*ptr)
    {
	fprintf(stderr, "%s: invalid number `%s'\n",
		progname, str);
	usage();
    }

    return value;
}


/* one-time setup of main data structures from the kernel */
setup()
{
    if ( (kmem=open("/dev/kmem", O_RDWR)) < 0 )
    {
	sprintf(buf, "%s: can't open /dev/kmem", progname);
	perror(buf);
	exit(1);
    }

    if (nlist("/etc/lddrv/kbd", myldsyms))
    {
	if (nlist("/unix", mysyms))
	{
	    sprintf(buf, "%s: can't nlist /unix", progname);
	    perror(buf);
	    exit(1);
	}
#ifdef DEBUG
	fputs("Using keymap from /unix\n", stderr);
#endif DEBUG
    }
    else
    {
#ifdef DEBUG
	fputs("Using ldkeymap from /etc/lddrv/kbd\n", stderr);
#endif DEBUG
	keymapaddr = ldkeymapaddr;
    }

#ifdef DEBUG
    fprintf(stderr, "keymap:	0x%08lx\n", keymapaddr);
#endif DEBUG
}


/* copy bytes from kernel address space to this process */
void kcopy(caddr, kaddr, nbytes)
char *caddr;
long kaddr;
long nbytes;
{
    if ( lseek(kmem, kaddr, 0)<0L ||
	read(kmem, caddr, (unsigned)nbytes) != nbytes )
    {
	sprintf(buf, "%s: can't read /dev/kmem", progname);
	perror(buf);
	exit(1);
    }
}


/* write bytes from this process' address space to the kernel's */
void kwrite(kaddr, caddr, nbytes)
long kaddr;
char *caddr;
long nbytes;
{
#ifdef DEBUG
    fprintf(stderr, "Writing %ld bytes to kernel address 0x%08lx\n",
	    nbytes, kaddr);
#endif

    if ( lseek(kmem, kaddr, 0)<0L ||
	write(kmem, caddr, (unsigned)nbytes) != nbytes )
    {
	sprintf(buf, "%s: can't write /dev/kmem", progname);
	perror(buf);
	exit(1);
    }
}


/* change the keymap of key `keynum' */
keyfix()
{
    struct keydef mydef;

    kcopy((char *)&mydef,
	  (long)&((struct keydef *)keymapaddr)[keynum],
	  (long)sizeof mydef);

    printf("Old keymap[0x%02x] = 0x%04x,0x%04x,0x%04x,%2d\n",
	   keynum,
	   mydef.kt_codes[0],
	   mydef.kt_codes[1],
	   mydef.kt_codes[2],
	   mydef.kt_flags);

    if (!(norm||shift||ctrl||flags))
	return 0;

    mydef.kt_codes[0] = norm;
    mydef.kt_codes[1] = shift;
    mydef.kt_codes[2] = ctrl;
    mydef.kt_flags = flags;

    printf("New keymap[0x%02x] = 0x%04x,0x%04x,0x%04x,%2d\n",
	   keynum,
	   mydef.kt_codes[0],
	   mydef.kt_codes[1],
	   mydef.kt_codes[2],
	   mydef.kt_flags);

    kwrite((long)&((struct keydef *)keymapaddr)[keynum],
	   (char *)&mydef,
	   (long)sizeof mydef);

    return 0;
}