[comp.sys.amiga] function keys in C

will1990@dunkirk.DKK (Mike Williamson) (09/25/89)

I'm writing a C program which opens a window, and allows for a series of
commands(window options, etc.). I've seen some source for the RAWKEY to 
ascii function(ex. 98 would be capslock), but I need to know what the 
code is for function keys 1 - 10.
I would also like the code for the Help key. Please E-mail me at 
the above address, unless you feel that posting it would help others too.

Thanks.
-mike

cmcmanis%pepper@Sun.COM (Chuck McManis) (09/26/89)

In article <215@dunkirk.DKK> will1990@dunkirk.UUCP (Mike Williamson) writes:
>I'm writing a C program which opens a window, and allows for a series of
>commands(window options, etc.). I've seen some source for the RAWKEY to 
>ascii function(ex. 98 would be capslock), but I need to know what the 
>code is for function keys 1 - 10.
>I would also like the code for the Help key. Please E-mail me at 
>the above address, unless you feel that posting it would help others too.

First off Mike you *don't* want the raw keycodes, ever. Cuz they can and
do change. The first person who runs your code and has the Dvorak keyboard
layout or the german one will complain that the keys don't match the keycaps.

Fortunately you can get done what you want and in an Intuition compatible
way, but to do that you will need to do a couple of things. I've excerpted
the relative code from the stuff I did for MicroEMACS 3.10, it is a little
bit different in that the code handling the keyconversions and Intuition 
events is actually a separate task so if something looks really weird 
remember the environment.

The secret is a routine called "dead key convert" which will convert 
key presses (RAWKEY) events into "real" characters. This code was written
by Bryce Nesbitt I believe and is part of an AmigaMail article. My code 
has another function called "convertkey" which takes the multicharacter
strings that DeadKeyConvert() returns and converts them into an internal
format that is used by emacs. In itself it probably isn't usable but it
should show you one way of doing it.

--Chuck
------------------------------stuff-------------------------
/*
 * key_handler.c
 *
 * This function actually runs as a subtask in AmigaDOS. It's job is to 
 * take input from the Window that emacs has opened and convert RAWKEY
 * and Mouse events into something that emacs can digest. It must be
 * compiled without stack checking because it has it's own context.
 *
 * It communicates with the parent task via a named message port. This
 * message port has the unique name 0x<address>EMACS where address is
 * the address of this task. The parent task gets this value when it
 * spawns the subtask and uses it to create a message port. This task
 * waits for that port to be created, and then creates it's own port
 * and sends that through to the parent. The parent passes the subtask
 * the IDCMP port address for the window and then begins waiting for 
 * messages on the subtask port. When the user wishes to exit, the 
 * parent task sends a "please kill yourself" signal to the subtask
 * who obligingly dies. Then the subtask sends a message that notifies
 * it's parent of it's demise.The parent then cleans up after it.
 *
 * There are two special messages that this task sends to the parent task.
 * The first is a number -n where n is the signal number it has allocated
 * as the signal to it that is should exit. The second is number -32 which
 * tells the parent process that it got the signal to die, everything is
 * cleaned up, The parent is then free to DeleteTask() the subtask.
 *
 * NOTE : Since this runs as a task and *not* as a process it can't do *any*
 * DOS related functions, or functions that would cause a DOS related function
 * to occur. That means *no* console I/O, no disk I/O, and no calls to Delay()
 * or any other DOS function.
 *
 * Written 21-Aug-89 By Chuck McManis
 * Last Update 15-Sept-89 By Chuck McManis
 *
 * Revision History  :
 *	- First Release.
 */

#include "estruct.h"

#ifdef AMIGA
#include <exec/types.h>
#include <exec/tasks.h>
#include <exec/ports.h>
#include <exec/devices.h>
#include <graphics/rastport.h>
#include <intuition/intuitionbase.h>
#include <intuition/intuition.h>
#include "amiga.h"

#define MSGBUFSIZE	256	/* Number of queued characters. */

/* Functions used within this module */
static short convert_key(unsigned char *, int, long);
static long DeadKeyConvert(struct IntuiMessage *, char *, int, struct KeyMap *);
static void error(int);

/* Data used within this module and global to all routines */
static struct IOStdReq		cio;  			
struct Device 			*ConsoleDevice = NULL; 	
static long			WinSig, MySignal;	
static struct MsgPort		*ereply;		
static struct EMACSMessage	emsg[MSGBUFSIZE]; 	/* Message Pool */
static struct ParentPort	*Parent = NULL;
static struct Task		*Me, *Mom;	
static long			MomsSignal, NxtMsg;

/* To calculate the Row/Column, given the mouse coordinates */
#define RPORT 		(im->IDCMPWindow->RPort)
#define WIN 		(im->IDCMPWindow)
#define COLUMN(x) 	(((x) - WIN->BorderLeft) / (RPORT->TxWidth))
#define ROW(y)	 	(((y) - WIN->BorderTop) / (RPORT->TxHeight))

/* These make it a bit easier to deal with the messages in route. */
#define IS_FREE(m)	((m)->e_Msg.mn_Node.ln_Type == NT_MESSAGE)
#define FREE(m)		(m)->e_Msg.mn_Node.ln_Type = NT_MESSAGE
#define	INBUFSIZE	32

/*
 * SendChars - This routine will send 1 or more characters on to the parent EMACS
 * process. The reason it is a routine rather than a simple PutMsg() is that some
 * "characters" like mouse movements or AREXX macros will expand into more than one
 * actual 8bit value.
 */
static void
SendChars(buf, len)
	char	*buf;
	int	len;
{
	short			i;
	struct EMACSMessage	*em;

	for (i=0; i < len; i++) {
		if (!IS_FREE(&emsg[NxtMsg])) {
			WaitPort(ereply);
			em = (struct EMACSMessage *)GetMsg(ereply);
			FREE(em);
			if (em == &(emsg[NxtMsg]))
				NxtMsg = (NxtMsg + 1) % MSGBUFSIZE;
		} else {
			em = &emsg[NxtMsg];
			NxtMsg = (NxtMsg + 1) % MSGBUFSIZE;
		}

		em->e_Char = *(buf+i);
		PutMsg(Parent, em);
	}

	return;
}

__saveds void
amiga_getkeys()
{
	struct IntuiMessage	*im;		  /* An IntuiMessage for mom  */
	struct MsgPort		*Window;	  /* The Parent's Window port */
	struct EMACSMessage	*em;		  /* Temporary pointer 	      */
	unsigned char		buf[INBUFSIZE];   /* Parent's port name       */
	int			i; 		  /* A temporary 	      */
	unsigned long		signals, sigmask; /* all of our signals       */
	short			ch;		  /* 16 bit character 	      */

 	/*
	 * NB: We do all of our allocating of resources before we actually
	 * get into business because this is the only time that we can unambiguously
	 * signal the parent that we have a problem. If everything gets allocated we
	 * assume we won't crash from then on.
 	 */
	
	/* 
	 * We need to open the console device so that we can use the
 	 * Keymap to process RAWKEY events.
	 */
    	setmem(&cio, sizeof(cio), 0); 
	if (OpenDevice("console.device", -1L, &cio, 0L))
		error(1);
	ConsoleDevice = (struct Device *) cio.io_Device;

	/* Let's find ourself. */
	Me = (struct Task *) FindTask(0L);

	/* Recreate the port name and go look for it. */
	sprintf(buf,"0x%xEMACS",(long) Me); /* the name */

	/* If this spins forever you are hosed anyway right? */
	while (!Parent)
		Parent = (struct ParentPort *)FindPort(buf);
	
	ereply = (struct MsgPort *)CreatePort(0,0); /* create a reply Port */
	if (!ereply)
		error(2);

	/* initialize all of our messages to the "free" state */
	for (i=0; i<MSGBUFSIZE; i++) {
		FREE(&emsg[i]);
		emsg[i].e_Msg.mn_ReplyPort = ereply;
		emsg[i].e_Msg.mn_Length = sizeof(struct EMACSMessage) - 
						sizeof(struct Message);
	}

	MySignal = AllocSignal(-1); /* I need a signal to listen on */
	if (MySignal == -1)
		error(3);

	/* Allocate a signal for the window port that we know about */
	WinSig = AllocSignal(-1);
	if (WinSig == -1)
		error(4);

	Window = Parent->pp_WPort; /* This is the IDCMP port of interest */

	/*
	 * Now take over the port from Mom. The Forbid/Permit pair keep Intuition
	 * From sending any messages when we are only halfway converted to our task
	 */
	Forbid();
	Mom = Window->mp_SigTask; 	/* let's make us the owner here. */
	Window->mp_SigTask = Me;  	/* by replacing the task pointer */
	MomsSignal = Window->mp_SigBit; /* And the signal bit with one */
	Window->mp_SigBit = WinSig;	/* That we allocated */
	Permit();

	/* Wake up on mysig, message from the window, or a reply. */
	sigmask = (1 << MySignal) + 
			(1 << Window->mp_SigBit) +
				(1 << ereply->mp_SigBit);

	/* Initialize all of the messages in our buffer. */
	NxtMsg = 0;
	
	/* Now tell mom we're ready to roll. ... */
	emsg[0].e_Char = -MySignal;
	PutMsg(Parent, &emsg[0]); /* Tell them our signal number */
	WaitPort(ereply); /* Wait for the reply. */

	(void) GetMsg(ereply); /* Remove the response. */

	/* Restore the message type. */
	FREE(&emsg[0]);

	/* loop "forever" */
	while (1) {
		signals = Wait(sigmask); /* We wait for something */

		/* Is it time to go away? */
		if (signals & (1 << MySignal)) {
			break; /* Time to leave */
		}

		/* Is it a reply to one of our messages */
		if (signals & (1 << ereply->mp_SigBit)) 
			/* Mark all returned messages as Free again */
			while (em = (struct EMACSMessage *) GetMsg(ereply))
				FREE(em);

		/* 
		 * Handle IntuiMessages 
		 *
		 * We always check for Intuimessages because sometimes we have
		 * to ignore them when we don't have any message units to
		 * pass them on in. So there may be some lingering here.
		 * 
		 * But first we check to see if we have a free emsg we can
		 * use, if not we skip processing the Intuimessage.
		 */

		if (! IS_FREE(&emsg[NxtMsg]))
			continue; 

		while (im = (struct IntuiMessage *)GetMsg(Window)) {

			switch (im->Class) {
				case RAWKEY :
					i = DeadKeyConvert(im, buf, 16, NULL);
					if (i == 0) 
						break;
					ch = convert_key(buf, i, im->Qualifier);
					if ((ch & 0xff00) != 0) {
						buf[0] = '\0';
						buf[1] = (ch >> 8) & 0xff;
						buf[2] = ch & 0xff;
						SendChars(buf, 3);
					} else {
						buf[0] = ch & 0xff;
						SendChars(buf, 1);
					}
					break;
				case MOUSEBUTTONS : 	/* Mouse */
					buf[0] = '\0';
					buf[1] = (MOUS >> 8) & 0xff;
					buf[2] = COLUMN(im->MouseX);
					buf[3] = ROW(im->MouseY);
					switch (im->Code) {
					case SELECTDOWN :
							buf[4] = 'a';
							break;
					case SELECTUP :
							buf[4] = 'b';
							break;
					case MENUDOWN : 
							buf[4] = 'e';
							break;
					case MENUUP :
							buf[4] = 'f';
							break;
					default : 
							buf[4] = 'x';
							break;
					}
					SendChars(buf, 5);
					break;
				default :
					break;
				} /* Switch statement */
			ReplyMsg(im);
		} /* While im != NULL */

	}
	/*
 	 * Ok, mama told us to go away and die, we'll do just that. 
	 */

	/* First, wait for all messages to be replied to. */
	while (1) {
		/* Check all to see if any aren't free. */
		for (i=0; i<MSGBUFSIZE; i++)
			if (! IS_FREE(&emsg[i]))
				break;
		if (i == MSGBUFSIZE) 
			break;

		/* If some are still in use, wait for them */
		WaitPort(ereply);
		while (em = (struct EMACSMessage *)GetMsg(ereply))
				FREE(em);
	}

	
	Forbid(); /* Now don't give away the processor for a moment. */

	/* Give back the IDCMP port to the parent task. */
	Window->mp_SigBit = MomsSignal;
	Window->mp_SigTask = Mom; 

	/* Free our allocated resources. */
	FreeSignal(WinSig);
	CloseDevice(&cio);
	DeletePort(ereply);
	FreeSignal(MySignal);

	Permit(); /* Ok, now we are "ready" to die. */

	/* Reinitialize msg zero to be a one way message */
	emsg[0].e_Msg.mn_ReplyPort = NULL; /* we 've freed it above */
	emsg[0].e_Msg.mn_Node.ln_Type = NT_MESSAGE;
	emsg[0].e_Char = -32; /* Special "goodbye" message. */

	Forbid(); /* These two have to be "atomic" */
	PutMsg(Parent, emsg); /* Send this final message to say we are done. */
	Wait(0L); /* Now wait forever, (which breaks the Forbid()) */
}

/* We found an error but let's die gracefully */
static void
error(n)
{
	if (ConsoleDevice)
		CloseDevice(ConsoleDevice);
	if (ereply) {
		DeletePort(ereply);
	}
	if (MySignal != -1)
		FreeSignal(MySignal);

	if (WinSig != -1)
		FreeSignal(WinSig);

	Forbid(); /* Now don't give away the processor for a moment. */
	emsg[0].e_Char = -(32 + n);
	emsg[0].e_Msg.mn_Node.ln_Type = NT_MESSAGE;
	emsg[0].e_Msg.mn_ReplyPort = NULL; 
	
	/* Now send the message and wait forever (Mom will remove us) */
	Forbid();
	PutMsg(Parent,emsg);
	Wait(0L);
}	

#define LEFTHANDED
/* Some comments : To make 8bit characters accessible and to allow one
 * to use "ALT-x" as a command key, we play a little game. If you use 
 * the LEFT Alt key on the Amiga we return ALT - Keycode, if you use 
 * the RIGHT Alt key on the keyboard we return the 8bit character.
 * The same is true with META. This lets us use Right Amiga to still
 * be a menu shortcut key.
 */

#ifdef LEFTHANDED
#define IS_ALTD(q) ((q) & IEQUALIFIER_LALT)
#else
#define IS_ALTD(q) ((q) & IEQUALIFIER_RALT)
#endif

#define IS_SHFT(q) (((q) & IEQUALIFIER_LSHIFT) || ((q) & IEQUALIFIER_RSHIFT))
#define IS_CTRL(q) ((q) & IEQUALIFIER_CONTROL)
#define IS_META(q) (((q) & IEQUALIFIER_LCOMMAND))

/* 
 * These map the ANSI strings returned by the key conversion into MicroEMACS
 * keycodes. They were  chosen to make sense with the default bindings. 
 * Note that the Keystring is *guaranteed* to remain constant from keymap to 
 * keymap. Although new keys on other keyboards won't show up here if they move
 * the "known" ones about they will still work, as will Dvorak keyboards etc 
 * etc.
 */
static struct {
		char	*str;	/* String to match */
		int	code;	/* Code to return */
	} xlate[30] = {
		{"Z", SPEC | '@'}, 	/* Shift TAB */
		{"A", SPEC | 'A'}, 	/* Up Arrow. */
		{"B", SPEC | 'B'}, 	/* Down Arrow. */
		{"C", SPEC | 'C'}, 	/* Right Arrow. */
		{"D", SPEC | 'D'}, 	/* Left Arrow. */
		{"T", SPEC | 'E'}, 	/* Shift Up Arrow. */
		{"S", SPEC | 'F'}, 	/* Shift Down Arrow. */
		{" A", SPEC | 'G'}, 	/* Shift Left. */
		{" @", SPEC | 'H'}, 	/* Shift Right. */
		{"0~", SPEC | 'P'},	/* F1 */
		{"1~", SPEC | 'Q'},	/* F2 */
		{"2~", SPEC | 'R'},	/* F3 */
		{"3~", SPEC | 'S'},	/* F4 */
		{"4~", SPEC | 'T'},	/* F5 */
		{"5~", SPEC | 'U'},	/* F6 */
		{"6~", SPEC | 'V'},	/* F7 */
		{"7~", SPEC | 'W'},	/* F8 */
		{"8~", SPEC | 'X'},	/* F9 */
		{"9~", SPEC | 'Y'},	/* F10 */
		{"10~", SPEC | 'p'},	/* Shift F1 */
		{"11~", SPEC | 'q'},	/* Shift F2 */
		{"12~", SPEC | 'r'},	/* Shift F3 */
		{"13~", SPEC | 's'},	/* Shift F4 */
		{"14~", SPEC | 't'},	/* Shift F5 */
		{"15~", SPEC | 'u'},	/* Shift F6 */
		{"16~", SPEC | 'v'},	/* Shift F7 */
		{"17~", SPEC | 'w'},	/* Shift F8 */
		{"18~", SPEC | 'x'},	/* Shift F9 */
		{"19~", SPEC | 'y'},	/* Shift F10 */
		{"?~", SPEC | '?'}	/* Help */
		};
/*
 * Utility function to convert the ANSI keycode that DeadKeyConvert
 * got for us, into a MicroEMACS keycode.
 */
static short
convert_key(buf, len, q)
	unsigned char	*buf;
	int		len;
	long		q;
{
	int	i;	/* Counter variable  	*/
	short	ch;	/* our character result */

	if (len == 1) 
		ch = buf[0];
	else if (*buf != '\x9b')
		ch = 0;
	else {
		for (i = 0, ch = 0; (i < 30) && (ch == 0); i++) 
			if (strncmp(buf+1, xlate[i].str, len-1) == 0)
				ch = xlate[i].code;
	}

	/* Check for any special flags */
	if (IS_CTRL(q))
		ch |= CTRL;
	if (IS_META(q)) {
		char	c;

		ch |= META;
		c = ch & 0xff;
		ch &= 0xff00;
		ch |= toupper(c);
	}
	if (IS_ALTD(q))
		ch |= ALTD;
	return (ch);
}

/*
 * DeadKeyConvert() : VERY IMPORTANT this lets you use those unique
 *		characters in other languages. It also handles converting
 *		the key codes into real characters no matter what keymap
 *		is set, so MicroEMACS works on everybodies Amiga. (Yeah!)
 * NB: The Alt key is used to generate 8 bit characters and "deadkey" type
 *     characters (like a with an umlat above it). Because of this it isn't
 *     normally available to emacs to use. Fortunately, the Amiga treats each
 *     Alt key separately, thus you can make one the method of getting 8 bit
 *     characters, and the other the "ALT" qualifier. 
 */
static long
DeadKeyConvert(msg, kbuffer, kbsize, kmap)
	struct IntuiMessage	*msg; 	    /* Our message        */
	char			*kbuffer;  /* Key buffer         */
	int			kbsize;   /* Size of buffer.    */
	struct KeyMap		*kmap;   /* The current KeyMap */
{
	struct InputEvent	ievent;

	if (msg->Code & IECODE_UP_PREFIX)
		return (0);

	/* Build an input event from the message */
	ievent.ie_NextEvent = NULL;
	ievent.ie_SubClass = 0;
	ievent.ie_Class = IECLASS_RAWKEY;
	ievent.ie_Code = msg->Code;
	ievent.ie_Qualifier = msg->Qualifier;
	/*
	 * This small bit of subterfuge makes sure that the "ALT" key that we are
	 * using for EMACS doesn't get interpreted into a deadkey of some sort.
	 */
#ifdef LEFTHANDED
	ievent.ie_Qualifier &= ~IEQUALIFIER_LALT; 
#else
	ievent.ie_Qualifier &= ~IEQUALIFIER_RALT; 
#endif
	ievent.ie_position.ie_addr = *((APTR *)msg->IAddress);
	return (RawKeyConvert(&ievent, kbuffer, kbsize, kmap));
}
#endif
--Chuck McManis
uucp: {anywhere}!sun!cmcmanis   BIX: cmcmanis  ARPAnet: cmcmanis@sun.com
These opinions are my own and no one elses, but you knew that didn't you.
"If I were driving a Macintosh, I'd have to stop before I could turn the wheel."