home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Carousel Volume 2 #1
/
carousel.iso
/
mactosh
/
hc
/
xcmd_pos.sit
/
PostEvent.c
< prev
next >
Wrap
C/C++ Source or Header
|
1987-10-15
|
11KB
|
318 lines
/*
* PostEvent XCMD : A HyperCard XCMD to post a key event. Useful for initiating QuicKeys sequences or
* Tempo macros. Hasn't been tested with AutoMac or any others that might exist.
* I use this to run applications with an accompanying QuicKeys sequence from Hypercard.
* Author: Dewi Williams. Delphi: DEWI. (303) 443 9038.
* Build notes: To build this, you'll need the latest MacTraps version (with the 2.11 upgrade), as
* well as the LSC SysEnvirons trap glue and header released by Think on Usenet.
* You'll also need a LSC version of Apple's MPW header "HyperXCmd.h".
* Usage: 1. PostEvent modifiers, virtual key. For instance "O",13 would be Option-w.
* The modifiers are C=command,S=shift, L=CapsLock and O=option.
* With QuicKeys only, c (lower case) = control -- if you have an extended keyboard.
* Also, beware of capsLock with QuicKeys - it's accepted, but isn't displayed.
* You can concatenate the modifiers together: "CO" is command-option.
* There's a table of virtual key values in Inside Macintosh V, and also in the
* stack supplied with this XCMD.
* 2. For QuicKeys only, Clicks or Sequences can be invoked by name instead:
* PostEvent "Reformat" invokes a sequence called "Reformat".
* Globals: PostEvent needs to know if Tempo or QuicKeys is running if you use the 2 argument
* call (the two behave somewhat differently).
* You can do this by assigning the name ("QuicKeys", "Tempo")
* to a HyperCard global called "Macro". Do this on an openStack message.
* Notes: Tempo needs both the charCode and the keyCode to function properly. Obtaining a
* keyCode from a charCode is problematic, but the inverse can be performed
* via the KeyTrans trap. This is the reason for the baroque calling parameters.
*/
#include <MacTypes.h>
#include <MemoryMgr.h>
#include <OSUtil.h>
#include <EventMgr.h>
#include <Environs.h> /* Defines for the SysEnvirons trap */
#include <ScriptMgr.h> /* Came with the 2.11 upgrade as "xScriptMgr.h" */
#include <HyperXCmd.h> /* Defines and structures for HyperCard XCMD programming */
#include "QuicKeys.h" /* Defines and structures for QuicKeys internals access */
/* Defines */
#define NULL 0L
#define HiWord(x) (((unsigned short *)&(x))[0])
#define LoWord(x) (((unsigned short *)&(x))[1])
#define controlKey 4096
/* Forward references */
unsigned CalcModifiers(StringPtr str);
void Post(Handle, int, int, Boolean);
pascal void
main(paramPtr)
register XCmdBlockPtr paramPtr;
{
Str255 str;
register unsigned modifiers;
int theKey;
Handle hKeyData;
SysEnvRec env;
register Boolean isTempo;
Handle global;
register int kchrID;
QuicInitBlock *qb;
register KeyRecord *kr;
/* Ensure that this is system 4.1 or later. This is needed for KeyTrans and extended keyboard
* checks. It ensures that KCHR/KMAP keyboard mappings are in place.
*/
if (SysEnvirons(1, &env) == envNotPresent) {
/* We beep rather than put up an error message because I don't like hardwiring English
* strings into programs, and haven't figured out an owned resource type of technique for
* putting the error messages into resources. Apple needs to set a standard here.
*/
SysBeep(1);
return;
}
/* Ensure that there are the correct number of parameters (either 1 or 2). */
if (paramPtr->paramCount > 2) {
SysBeep(1);
return;
}
/* Calculate the resource ID of the relevant KCHR resource. The Script Manager has
* this information. This may be useful if you're using Dvorak mappings, for instance.
* I haven't really been able to test this - 0 is the US default, and it's the only one I
* have. I wonder how Tempo works under KanjiTalk, anyway...
*/
kchrID = (int)GetScript(GetEnvirons(smKeyScript), smScriptKeys);
if ( (hKeyData = GetResource('KCHR', kchrID)) == NULL) {
/* We've confirmed that it is System 4.1 or later. Strange. */
SysBeep(1);
return;
}
/* If there's only one parameter, it's assumed to be a named QuicKeys sequence or click. */
if (paramPtr->paramCount == 1) {
/* Find the QuicKeys data block in the system heap. */
if ( (qb = FindSysHeap()) == NULL) {
SysBeep(1);
return;
}
/* Retrieve the name. */
ZeroToPas(paramPtr,*(paramPtr->params[0]), str);
/* Attempt to find the named key record in the program keys, then universal keys. Pascaloids
* please note - we're relying on short-circuit evaluation here.
*/
if (((kr = FindKeyRecord(qb, str, TRUE)) != NULL) || ((kr = FindKeyRecord(qb, str, FALSE)) != NULL)) {
theKey = kr->key >> 8;
Post(hKeyData, theKey, kr->modifiers, FALSE);
return;
}
/* Not found. */
SysBeep(1);
} else {
/* Two parameters. Could be either Tempo or QuicKeys. Checking for QuicKeys in memory
* isn't sufficient, since they can sort of co-exist (!). So to disambiguate, we check a Hypercard
* global called "Macro".
*/
/* This version supports only QuicKeys and Tempo. QuicKeys is the default. */
global = GetGlobal(paramPtr, (StringPtr)"\pMacro");
if ((**global) == 0) { /* Empty string */
/* There isn't a global called Macro, or it's empty. In this case we have to do a check for
* the presence of QuicKeys (or toss a coin!).
*/
isTempo = (FindSysHeap() == NULL) ? TRUE : FALSE;
} else {
ZeroToPas(paramPtr, *global, str);
isTempo = StringEqual(paramPtr, (Str31 *)str, (Str31 *)"\pTempo");
}
DisposHandle(global); /* Finished with the global. */
/* First param is the modifiers value. Convert it to a pascal string */
ZeroToPas(paramPtr,*(paramPtr->params[0]), str);
/* Calculate the modifiers from the string */
modifiers = CalcModifiers(str);
if (isTempo == FALSE) { /* ! Tempo assumed to be QuicKeys. */
/* QuicKeys handles Control key sequences on the extended keyboard. Tempo doesn't seem to. */
if (modifiers&controlKey) {
/* Make no assumptions about new keyboard models! */
if (env.keyBoardType <= envStandADBKbd && env.keyBoardType != envAExtendKbd) {
SysBeep(1);
return;
}
}
} else {
/* Tempo 1.2 doesn't handle the control modifier. */
if (modifiers&controlKey) {
SysBeep(1);
return;
}
}
/* The second param is the virtual keycode. */
ZeroToPas(paramPtr,*(paramPtr->params[1]), str);
theKey = ((int)StrToNum(paramPtr, (Str31 *)str)) & 0x7F;
/* Sanity check: virtual keycodes past 0x60 belong to the extended keyboard (function keys, PageUp
* etc. To stop any possible confusion, we check for the existence of the extended keyboard before
* posting any such event.
*/
if (theKey > 0x60 && env.keyBoardType != envAExtendKbd) {
SysBeep(1);
return;
}
Post(hKeyData, theKey, modifiers, isTempo);
}
}
/*
* This is the code that actually posts the key event(s).
*/
void
Post(hKeyData, theKey, modifiers, isTempo)
Handle hKeyData;
int theKey;
int modifiers;
Boolean isTempo;
{
register unsigned msg;
long kResult;
long state = 0;
EvQElPtr postEntry;
/* The keyCode parameter passed to the KeyTrans trap consists of the modifier
* flags in bits 8-15, up/down stroke in bit 7 (1 = up), and the virtual key
* code in bits 6 - 0. With Tempo, we just need to know what the plain keystroke is,
* so we pass 0 for modifiers. QuicKeys seems to need both. This is all empirical - I'm not
* sure why, though I suspect it's something like the difference between a prologue &
* epilogue trap patch.
*/
modifiers |= btnState; /* The mouse button is UP */
if (isTempo == FALSE) theKey |= modifiers; /* Already in bits 8-15 */
/* I'm assuming here that KeyTrans doesn't move the heap. Don't see why it should. */
kResult = KeyTrans(*hKeyData, theKey, &state);
/* kResult consists of 2 16 bit characters to be posted as events (usually the high word of each is 0),
* high word first. A return value of 0 in either word should not be posted.
* Tempo needs both the keyCode and the charCode fields to be posted. So we use KeyTrans to get
* a charCode value from the keyCode, and then add both together and post the result.
*/
theKey &= 0x7F; /* Cut out any modifiers */
theKey <<= 8; /* And shift to the keyCode position */
if ( (msg = HiWord(kResult)) != 0) {
msg |= theKey;
PPostEvent(keyDown, msg, &postEntry);
/* And tack on the modifiers. */
postEntry->evtQModifiers = modifiers;
}
if ( (msg = LoWord(kResult)) != 0) {
msg |= theKey;
PPostEvent(keyDown, msg, &postEntry);
/* And tack on the modifiers. */
postEntry->evtQModifiers = modifiers;
}
}
/* Convert the modifier string to its numeric equivalent. This code isn't perfect - it does
* blind matches without validating for odd characters. For instance "LXO" == "LO".
*/
static unsigned
CalcModifiers(str)
register StringPtr str;
{
register StringPtr end = str + str[0] + 1;
register unsigned modifiers = 0;
for (str++;str < end; str++) {
switch( (*str) ) {
case 'C':
modifiers |= cmdKey;
break;
case 'S':
modifiers |= shiftKey;
break;
case 'L':
modifiers |= alphaLock;
break;
case 'O':
modifiers |= optionKey;
break;
case 'c':
modifiers |= controlKey;
break;
}
}
return modifiers;
}
/*
* This is a C version of the example code in Chapter 8 of the QuicKeys manual.
*/
QuicInitBlock *
FindSysHeap()
{
register Ptr endBlk = SysZone->bkLim;
register QuicInitBlock *qp;
qp = (QuicInitBlock *) &SysZone->heapData;
while(qp != (QuicInitBlock *) endBlk) {
/* Analyze the block we're looking at. */
if ((qp->header[0] & 0xC0) == 0x40) { /* Is it non-relocatable? */
/* Check magic and signature. */
if (qp->quic.magic == 0xa89f1234 && qp->quic.signature == 'CELN') {
/* Chapter 8 states that the version number is 1. It's actually 0 (as the example
* assembly language shows and a quick call to CE Software confirmed).
*/
if (qp->quic.version == 0) return qp;
}
}
/* Time to move on to the next block. The 0xFFFFFF strips off the tag byte(s). */
qp = (QuicInitBlock *)(((Byte *)qp) + ((* (long *)&qp->header) & 0xFFFFFF));
}
return NULL; /* Failed to find it */
}
/*
* Find a named key record (Note: only clicks & sequences are named).
*/
KeyRecord *
FindKeyRecord(qb, name, progKeys)
QuicInitBlock *qb;
register StringPtr name;
Boolean progKeys;
{
register KeyRecord *kr = (progKeys == TRUE) ? qb->quic.application : qb->quic.universal;
register KeyRecord *end = kr + N_QCKEYS;
for (; kr < end; kr++) {
switch(kr->QKtype) {
case QK_SEQUENCE:
if (IUCompString(name, kr->u.QuicSequence.title) == 0) return kr;
break;
case QK_CLICK:
if (IUCompString(name, kr->u.QuicClick.title) == 0) return kr;
default: /* Doesn't have a name */
break;
}
}
return NULL;
}