home *** CD-ROM | disk | FTP | other *** search
/ Carousel Volume 2 #1 / carousel.iso / mactosh / hc / xcmd_pos.sit / PostEvent.c < prev    next >
C/C++ Source or Header  |  1987-10-15  |  11KB  |  318 lines

  1. /*
  2.   * PostEvent XCMD :    A HyperCard XCMD to post a key event. Useful for initiating QuicKeys sequences or
  3.   *                    Tempo macros. Hasn't been tested with AutoMac or any others that might exist.
  4.   *                    I use this to run applications with an accompanying QuicKeys sequence from Hypercard.
  5.   * Author:            Dewi Williams. Delphi: DEWI.  (303) 443 9038.
  6.   * Build notes:            To build this, you'll need the latest MacTraps version (with the 2.11 upgrade), as
  7.   *                    well as the LSC SysEnvirons trap glue and header released by Think on Usenet.
  8.   *                    You'll also need a LSC version of Apple's MPW header "HyperXCmd.h".
  9.   * Usage:                1.    PostEvent modifiers, virtual key. For instance "O",13 would be Option-w.
  10.   *                        The modifiers are C=command,S=shift, L=CapsLock and O=option.
  11.   *                        With QuicKeys only, c (lower case) = control -- if you have an extended keyboard.
  12.   *                        Also, beware of capsLock with QuicKeys - it's accepted, but isn't displayed.
  13.   *                        You can concatenate the modifiers together: "CO" is command-option.
  14.   *                        There's a table of virtual key values in Inside Macintosh V, and also in the
  15.   *                        stack supplied with this XCMD.
  16.   *                    2.    For QuicKeys only, Clicks or Sequences can be invoked by name instead:
  17.   *                        PostEvent "Reformat" invokes a sequence called "Reformat".
  18.   * Globals:            PostEvent needs to know if Tempo or QuicKeys is running if you use the 2 argument
  19.   *                    call (the two behave somewhat differently). 
  20.   *                    You can do this by assigning the name ("QuicKeys", "Tempo") 
  21.   *                    to a HyperCard global called "Macro". Do this on an openStack message.
  22.   * Notes:                Tempo needs both the charCode and the keyCode to function properly. Obtaining a 
  23.   *                    keyCode from a charCode is problematic, but the inverse can be performed
  24.   *                    via the KeyTrans trap.  This is the reason for the baroque calling parameters.
  25.   */
  26.  
  27. #include <MacTypes.h>
  28. #include <MemoryMgr.h>
  29. #include <OSUtil.h>
  30. #include <EventMgr.h>
  31. #include <Environs.h>            /* Defines for the SysEnvirons trap */
  32. #include <ScriptMgr.h>            /* Came with the 2.11 upgrade as "xScriptMgr.h" */
  33. #include <HyperXCmd.h>            /* Defines and structures for HyperCard XCMD programming */
  34. #include "QuicKeys.h"            /* Defines and structures for QuicKeys internals access */
  35.  
  36. /* Defines */
  37. #define    NULL            0L
  38. #define    HiWord(x)        (((unsigned short *)&(x))[0])
  39. #define    LoWord(x)        (((unsigned short *)&(x))[1])
  40. #define    controlKey    4096
  41.  
  42. /* Forward references */
  43. unsigned        CalcModifiers(StringPtr str);
  44. void            Post(Handle, int, int, Boolean);
  45.  
  46. pascal void
  47. main(paramPtr)
  48. register XCmdBlockPtr    paramPtr;
  49. {
  50.     Str255            str;
  51.     register unsigned    modifiers;
  52.     int                theKey;
  53.     Handle            hKeyData;
  54.     SysEnvRec        env;
  55.     register Boolean    isTempo;
  56.     Handle            global;
  57.     register int        kchrID;
  58.     QuicInitBlock         *qb;
  59.     register KeyRecord    *kr;
  60.  
  61.     /* Ensure that this is system 4.1 or later.  This is needed for KeyTrans and extended keyboard
  62.       * checks. It ensures that KCHR/KMAP keyboard mappings are in place.
  63.       */
  64.     if (SysEnvirons(1, &env) == envNotPresent) {
  65.         /* We beep rather than put up an error message because I don't like hardwiring English
  66.           * strings into programs, and haven't figured out an owned resource type of technique for
  67.           * putting the error messages into resources. Apple needs to set a standard here.
  68.           */
  69.         SysBeep(1);
  70.         return;
  71.     }
  72.  
  73.     /* Ensure that there are the correct number of parameters (either 1 or 2). */
  74.     if (paramPtr->paramCount > 2) {
  75.         SysBeep(1);
  76.         return;
  77.     }
  78.  
  79.     /* Calculate the resource ID of the relevant KCHR resource. The Script Manager has
  80.       * this information. This may be useful if you're using Dvorak mappings, for instance.
  81.       * I haven't really been able to test this - 0 is the US default, and it's the only one I
  82.       * have. I wonder how Tempo works under KanjiTalk, anyway...
  83.       */
  84.     kchrID = (int)GetScript(GetEnvirons(smKeyScript), smScriptKeys);
  85.  
  86.     if ( (hKeyData = GetResource('KCHR', kchrID)) == NULL) {
  87.         /* We've confirmed that it is System 4.1 or later. Strange. */
  88.         SysBeep(1);
  89.         return;
  90.     }
  91.  
  92.     /* If there's only one parameter, it's assumed to be a named QuicKeys sequence or click. */
  93.     if (paramPtr->paramCount == 1) {
  94.         /* Find the QuicKeys data block in the system heap. */
  95.         if ( (qb = FindSysHeap()) == NULL) {
  96.             SysBeep(1);
  97.             return;
  98.         }
  99.  
  100.         /* Retrieve the name. */
  101.         ZeroToPas(paramPtr,*(paramPtr->params[0]), str);
  102.  
  103.         /* Attempt to find the named key record in the program keys, then universal keys. Pascaloids
  104.           * please note - we're relying on short-circuit evaluation here.
  105.           */
  106.         if (((kr = FindKeyRecord(qb, str, TRUE)) != NULL) || ((kr = FindKeyRecord(qb, str, FALSE)) != NULL)) {
  107.             theKey = kr->key >> 8;
  108.             Post(hKeyData, theKey, kr->modifiers, FALSE);
  109.             return;
  110.         }
  111.  
  112.         /* Not found. */
  113.         SysBeep(1);
  114.     } else {
  115.         /* Two parameters. Could be either Tempo or QuicKeys. Checking for QuicKeys in memory 
  116.           * isn't sufficient, since they can sort of co-exist (!). So to disambiguate, we check a Hypercard
  117.           * global called "Macro".
  118.           */
  119.  
  120.         /* This version supports only QuicKeys and Tempo. QuicKeys is the default. */
  121.         global = GetGlobal(paramPtr, (StringPtr)"\pMacro");
  122.  
  123.         if ((**global) == 0) {            /* Empty string */
  124.             /* There isn't a global called Macro, or it's empty. In this case we have to do a check for
  125.               * the presence of QuicKeys (or toss a coin!).
  126.               */
  127.             isTempo = (FindSysHeap() == NULL) ? TRUE : FALSE;
  128.         } else {
  129.             ZeroToPas(paramPtr, *global, str);
  130.             isTempo = StringEqual(paramPtr, (Str31 *)str, (Str31 *)"\pTempo");
  131.         }
  132.         DisposHandle(global);            /* Finished with the global. */
  133.  
  134.         /* First param is the modifiers value.  Convert it to a pascal string */
  135.         ZeroToPas(paramPtr,*(paramPtr->params[0]), str);
  136.  
  137.         /* Calculate the modifiers from the string */
  138.         modifiers = CalcModifiers(str);
  139.  
  140.         if (isTempo == FALSE) {                /* ! Tempo assumed to be QuicKeys.  */
  141.             /* QuicKeys handles Control key sequences on the extended keyboard. Tempo doesn't seem to. */
  142.             if (modifiers&controlKey) {
  143.                 /* Make no assumptions about new keyboard models! */
  144.                 if (env.keyBoardType <= envStandADBKbd && env.keyBoardType != envAExtendKbd) {
  145.                     SysBeep(1);
  146.                     return;
  147.                 }
  148.             }
  149.         } else {
  150.             /* Tempo 1.2 doesn't handle the control modifier. */
  151.             if (modifiers&controlKey) {
  152.                 SysBeep(1);
  153.                 return;
  154.             }
  155.         }
  156.  
  157.         /* The second param is the virtual keycode.  */
  158.         ZeroToPas(paramPtr,*(paramPtr->params[1]), str);
  159.         theKey = ((int)StrToNum(paramPtr, (Str31 *)str)) & 0x7F;
  160.  
  161.         /* Sanity check: virtual keycodes past 0x60 belong to the extended keyboard (function keys, PageUp
  162.           * etc. To stop any possible confusion, we check for the existence of the extended keyboard before
  163.           * posting any such event.
  164.           */
  165.         if (theKey > 0x60 && env.keyBoardType != envAExtendKbd) {
  166.             SysBeep(1);
  167.             return;
  168.         }
  169.  
  170.         Post(hKeyData, theKey, modifiers, isTempo);
  171.     }
  172. }
  173.  
  174. /*
  175.   * This is the code that actually posts the key event(s).
  176.   */
  177.  
  178. void
  179. Post(hKeyData, theKey, modifiers, isTempo)
  180. Handle    hKeyData;
  181. int        theKey;
  182. int        modifiers;
  183. Boolean    isTempo;
  184. {
  185.     register unsigned    msg;
  186.     long                kResult;
  187.     long                state = 0;
  188.     EvQElPtr            postEntry;
  189.  
  190.     /* The keyCode parameter passed to the KeyTrans trap consists of the modifier
  191.       * flags in bits 8-15, up/down stroke in bit 7 (1 = up), and the virtual key
  192.       * code in bits 6 - 0. With Tempo, we just need to know what the plain keystroke is, 
  193.       * so we pass 0 for modifiers. QuicKeys seems to need both. This is all empirical - I'm not
  194.       * sure why, though I suspect it's something like the difference between a prologue &
  195.       * epilogue trap patch.
  196.       */
  197.  
  198.     modifiers |= btnState;                    /* The mouse button is UP */
  199.     if (isTempo == FALSE)  theKey |= modifiers;    /* Already in bits 8-15 */
  200.  
  201.     /* I'm assuming here that KeyTrans doesn't move the heap. Don't see why it should. */
  202.     kResult = KeyTrans(*hKeyData, theKey, &state);
  203.  
  204.     /* kResult consists of 2 16 bit characters to be posted as events (usually the high word of each is 0),
  205.       * high word first. A return value of 0 in either word should not be posted.
  206.       * Tempo needs both the keyCode and the charCode fields to be posted. So we use KeyTrans to get
  207.       * a charCode value from the keyCode, and then add both together and post the result.
  208.       */
  209.     theKey &= 0x7F;            /* Cut out any modifiers */
  210.     theKey <<= 8;                /* And shift to the keyCode position */
  211.  
  212.     if ( (msg = HiWord(kResult)) != 0) {
  213.         msg |= theKey;
  214.         PPostEvent(keyDown, msg, &postEntry);
  215.  
  216.         /* And tack on the modifiers. */
  217.         postEntry->evtQModifiers = modifiers;
  218.     }
  219.  
  220.     if ( (msg = LoWord(kResult)) != 0) {
  221.         msg |= theKey;
  222.         PPostEvent(keyDown, msg, &postEntry);
  223.  
  224.         /* And tack on the modifiers. */
  225.         postEntry->evtQModifiers = modifiers;
  226.     }
  227. }
  228.  
  229. /* Convert the modifier string to its numeric equivalent. This code isn't perfect - it does
  230.   * blind matches without validating for odd characters. For instance "LXO" == "LO".
  231.   */
  232.  
  233. static unsigned
  234. CalcModifiers(str)
  235. register StringPtr    str;
  236. {
  237.     register StringPtr    end = str + str[0] + 1;
  238.     register unsigned    modifiers =  0;
  239.  
  240.     for (str++;str < end; str++) {
  241.         switch( (*str) ) {
  242.         case 'C':
  243.             modifiers |= cmdKey;
  244.             break;
  245.         case 'S':
  246.             modifiers |= shiftKey;
  247.             break;
  248.         case 'L':
  249.             modifiers |= alphaLock;
  250.             break;
  251.         case 'O':
  252.             modifiers |= optionKey;
  253.             break;
  254.         case 'c':
  255.             modifiers |= controlKey;
  256.             break;
  257.         }
  258.     }
  259.     return modifiers;
  260. }
  261.  
  262. /*
  263.   * This is a  C version of the example code in Chapter 8 of the QuicKeys manual.
  264.   */
  265.  
  266. QuicInitBlock *
  267. FindSysHeap()
  268. {
  269.     register Ptr            endBlk = SysZone->bkLim;
  270.     register QuicInitBlock    *qp;
  271.     
  272.     qp = (QuicInitBlock *) &SysZone->heapData;
  273.  
  274.     while(qp != (QuicInitBlock *) endBlk) {
  275.         /* Analyze the block we're looking at. */
  276.         if ((qp->header[0] & 0xC0) == 0x40) {                /* Is it non-relocatable? */
  277.             /* Check magic and signature. */
  278.             if (qp->quic.magic == 0xa89f1234 && qp->quic.signature == 'CELN') {
  279.                 /* Chapter 8 states that the version number is 1. It's actually 0 (as the example
  280.                   * assembly language shows and a quick call to CE Software confirmed).
  281.                   */
  282.                 if (qp->quic.version == 0) return qp;
  283.             }
  284.         }
  285.  
  286.         /* Time to move on to the next block. The 0xFFFFFF strips off the tag byte(s). */
  287.         qp = (QuicInitBlock *)(((Byte *)qp) + ((* (long *)&qp->header) & 0xFFFFFF));
  288.     }
  289.     return NULL;        /* Failed to find it */
  290. }
  291.  
  292. /*
  293.   * Find a named key record (Note: only clicks & sequences are named).
  294.   */
  295.  
  296. KeyRecord *
  297. FindKeyRecord(qb, name, progKeys)
  298. QuicInitBlock         *qb;
  299. register StringPtr    name;
  300. Boolean             progKeys;
  301. {
  302.     register KeyRecord    *kr = (progKeys == TRUE) ? qb->quic.application : qb->quic.universal;
  303.     register KeyRecord    *end = kr + N_QCKEYS;
  304.     
  305.     for (; kr < end; kr++) {
  306.         switch(kr->QKtype) {
  307.         case QK_SEQUENCE:
  308.             if (IUCompString(name, kr->u.QuicSequence.title) == 0) return kr;
  309.             break;            
  310.         case QK_CLICK:
  311.             if (IUCompString(name, kr->u.QuicClick.title) == 0) return kr;        
  312.         default:                                /* Doesn't have a name */
  313.             break;
  314.         }
  315.     }
  316.     return NULL;
  317. }
  318.