💾 Archived View for mirrors.apple2.org.za › archive › apple.cabi.net › Hypercard › Xcmds › XCMD.XFCN… captured on 2023-05-25 at 00:21:41.

View Raw

More Information

-=-=-=-=-=-=-

XCMD and XFCN: The Magic Hooks That Extend HyperTalk

This documentation by Andy Stadler and Darin Acquistapace
For HyperCard IIGS 1.1

(Thanks to Ted Kaehler who wrote the original of this paper)


XCMD's and XFCN's are a powerful method of extending the capabilities 
of HyperCard IIGS.  Quite simply, an XCMD or XFCN is a code resource 
containing any compiled code the user wishes.  XCMD's and XFCN's can be 
used where HyperTalk might be too slow (eg complicated sorts), as 
device or hardware controllers (eg LaserDisk controllers) or for 
anything else the programmer desires.

A note on terminology.  For most purposes, XCMD's and XFCN's are 
identical, so unless necessary to draw a distinction, this document 
will use the term XCMD to describe both types of X-thing.

XCMD's are inserted into the normal search path, in locations similar 
to standard HyperTalk handlers.  The resource forks of the current 
stack, home stack and hypercard itself are searched and any XCMD 
therein becomes part of the language.  Just as a handler may either 
extend the language or actually replace features, so may an XCMD.  The 
inheritance path is:

Button (or Field)
Card
Stack
Any XCMD currently open because it owns an XWindow(s).
Stack XCMD
Home
Home XCMD
XCMD in HyperCard application file
HyperCard command

An XCMD or XFCN is a code resource of types $801E and $801F 
respectively.  In addition name resources (type $8014) must exist 
because the language can only reference these resources by name.  You 
can create an XCMD and it's name with RezIIGS,  and you can move them 
from file to file with RMover, the ResCopy XCMD, or with any other 
resource moving tool.

XCMD's are called using the standard Pascal/toolbox stack based method.  
They are called in full native mode, with a single parameter pushed 
onto the stack.  On exit, the XCMD must pop the parameter, and must 
return with the same processor mode and direct page.  All other 
registers may be trashed.  These rules are important for assembly 
language programmers but high-level languages use glue to take care of 
all the "dirty work."

Once inside the XCMD, the programmer has almost limitless capabilities.  
The user may request memory, and make OS or ToolBox calls.  New 
extensions in HyperCard IIGS version 1.1 allow XCMD's to own windows 
and interact with the user through them.  The only practical limitation 
of XCMD's is that the entire package including all code and data must 
fit in a single OMF segment - giving an upper bound of 64k!

An XCMD can be expressed in Pascal with the following description:

PROCEDURE EntryPoint(paramBlock: XCmdPtr);

All information is interchanged with HyperCard IIGS through a structure 
called an  XCmdBlock.  Note that this is the only parameter ever passed 
to an XCMD.  If the HyperTalk script passes any parameters, they will 
be installed into the parameter block itself.  The XCmdBlock appears as 
follows:

XCmdPtr = ^XCmdBlock; 
XCmdBlock = RECORD
     paramCount:    INTEGER;
     params:   ARRAY[1..16] OF Handle;
     returnValue:   Handle;
     passFlag: BOOLEAN;
     userID:   INTEGER;
     returnStat:    BOOLEAN;
            END;

The fields are defined as follows:

paramCount is the number of arguments in the XCMD invocation.  This 
does not include the XMCD name itself.  For example, the command Flash 
5 has a parmCount of 1.

params is an array of handles.  Each handle contains a zero-terminated 
string containing a single parameter from the invocation.  For example, 
the command Flash 5 will create a single handle in params[1] containing 
'5' and a zero terminater.  The other 15 entries are undefined.

returnValue is used in three different ways.  For XCMD's,  a string 
returned here will be placed into "the result".  For  XFCN's, the 
function result is returned here, and "the result" is never affected.  
Finally, for either type, if returnStat is TRUE, the string here will 
be displayed as a script error (see returnStat below).

In either case, the result is returned by creating a new handle and 
putting the result in it (zero terminated string).  HyperCard IIGS will 
dispose the handle.  The field is initialized to NIL before calling the 
XCMD, so if you wish to return the empty string, just leave the field 
alone.

passFlag is a flag telling HyperCard IIGS how to complete the message.  
If it returns FALSE (its unmodified state) the XCMD has handled the 
message.  If the code sets passFlag to TRUE, the message will continue 
to pass through the message heirarchy.  Setting passFlag to true is 
exactly equivalent to invoking the command pass messageName in a 
HyperTalk handler.

userID is a IIGS user ID.  A unique ID will be assigned to each 
concurrent invocation of each different XCMD.  That is, no two running 
XCMDs will ever be given the same ID at the same time, even if they 
recurse into each other.  The XCMD should use this ID for all memory 
requests.  Please clean up all your handles before exiting!

returnStat allows XCMDs to report an error using the script error 
reporting mechanism.  If returnStat is TRUE, the returnValue will be 
displayed in a script error dialog box, along with "script" or "cancel" 
buttons.  This allows XCMD to cleanly report errors in parameters or 
usage.


MACHINE STATE ON XCMD LAUNCH

When you launch an XCMD, the following machine state may be depended 
on:

  Full native 16 bit mode.
  The current grafport is the card window.
  Resource search path is set to infinite search from the current stack 
file on down.


MAKING CALLBACKS

At any time, an XCMD may call back into HyperCard IIGS.  From a high-
level language the call descriptions below should be enough.  For 
assembly, the following notes should help:

o     The callback routines are accessed similar to pascal or toolbox 
procedures:  push a result space if function, push parameters.  The X 
register is loaded with the call #, and the "HyperCard IIGS Vector" is 
then called as a subroutine:  JSL $E10220.  Callbacks follow mostly the 
same rules as Tool Calls:  -- All params and results are passed on the 
stack -- The A,X,Y registers are undefined (note: no error codes or C 
flag) -- Databank and Direct Page -will- be preserved.
o     HyperCard IIGS assumes that XCMDs will place their global data 
adjacent to the XCMD code.  Therefore, to aid the XCMD, the data bank 
will be set to the same bank as the XCMD's code before it is called.  
If the XCMD wishes to place globals elsewhere, it is free to modify the 
data bank register, as HyperCard IIGS will restore the bank register 
correctly when the XCMD completes.
o    The technique for returning strings as function results is as 
follows:  (1)  Push a pointer to a string-sized result space.  (2)  
Push params.  (3)  Load X with function number.  (4)  Call entry point.  
Callback will pop params, but leaves the result pointer, so (5)  pop 
the result pointer.  This is the method used by the MPW PascalIIGS 
compiler.

In addition, notice that several of the calls create and return a new 
handle;  this handle is not created with the XCMD's user ID.  Do not 
ever DisposeAll(parms^.userID), for three reasons:  1.  If you have 
created data and passed it back through a callback, HyperCard IIGS 
might still be using it;   2.  The user ID's may be re-used, and some 
XCMD's keep data around from one invocation to the next.   If you 
DisposeAll with your current userID, you might destroy data held by an 
earlier XCMD.  3.  The user ID is preserved when calling an XCMD that 
owns XWindows in response to window events, a single XCMD may own 
multiple windows and executing a DisposeAll will release memory that 
may still be required in conjunction with other windows.

The following callbacks have been defined:

PROCEDURE SendCardMessage(msg: Str255); Asm #1

Send a HyperCard message (a command with arguments) to the current 
card.



FUNCTION EvalExpr(expr: Str255): Handle;     Asm #2

Evaluate a HyperCard expression and return the answer.  The answer is a 
handle to a zero-terminated string.

FUNCTION StringLength(strPtr: Ptr): LongInt; Asm #3

Count the characters from where strPtr points until the next zero byte. 
Does not count the zero itself. strPtr must be a zero-terminated 
string.

FUNCTION StringMatch(pattern: Str255; target: Ptr): Ptr;    Asm #4

Perform case-insensitive match looking for pattern anywhere in  target, 
returning a pointer to first character of the first match,  in  target 
or NIL if no match found. pattern is a Pascal string,  and target is a 
zero-terminated string.

PROCEDURE SendHCMessage(msg: Str255);   Asm #5

Send a HyperCard message (a command with arguments) to HyperCard.

PROCEDURE ZeroBytes(dstPtr: Ptr; longCount: LongInt);  Asm #6

Write zeros into memory starting at dstPtr and going for longCount 
number of bytes.

FUNCTION PasToZero(str: Str255): Handle;     Asm #7

Convert a Pascal string to a zero-terminated string. Returns a handle 
to a new  zero-terminated string. The caller must dispose the handle.

PROCEDURE ZeroToPas(zeroStr: Ptr; VAR pasStr: Str255); Asm #8

Fill the Pascal string with the contents of the zero-terminated string. 
You create the Pascal string and pass it in as a VAR parameter. Useful 
for converting the arguments of any XCMD to Pascal strings.

FUNCTION StrToLong(str: Str31): LongInt;     Asm #9

Convert a string of ASCII decimal digits to an unsigned long integer.

FUNCTION StrToNum(str: Str31): LongInt; Asm #10

Convert a string of ASCII decimal digits to a signed long integer.  
Negative sign is allowed.

FUNCTION StrToBool(str: Str31): BOOLEAN;     Asm #11

Convert the Pascal strings 'true' and 'false' to booleans.

FUNCTION StrToExt(str: Str31): Extended;     Asm #12

Convert a string of ASCII decimal digits to an extended floating point 
value.




FUNCTION LongToStr(posNum: LongInt): Str31;  Asm #13

Convert an unsigned long integer to a Pascal string.

FUNCTION NumToStr(num: LongInt): Str31; Asm #14

Convert a signed long integer to a Pascal string.

FUNCTION NumToHex(num: LongInt; nDigits: INTEGER): Str31; Asm #15

Convert an unsigned long integer to a hexadecimal number and put it 
into a Pascal string.

FUNCTION BoolToStr(bool: BOOLEAN): Str31;    Asm #16

Convert a BOOLEAN to 'true' or 'false'.

FUNCTION ExtToStr(num: Extended): Str31;     Asm #17

Convert an extended long integer to decimal digits in a string.

FUNCTION GetGlobal(globName: Str255): Handle;     Asm #18

Return a new handle to a zero-terminated string containing the value of 
the specified HyperTalk global variable.  The caller must dispose this 
handle.

PROCEDURE SetGlobal(globName: Str255; globValue: Handle); Asm #19

Set the value of the specified HyperTalk global variable to be the 
zero-terminated string in globValue. The contents of the Handle are 
copied, so you must still dispose it afterwards.

FUNCTION GetFieldByName(cardFieldFlag: BOOLEAN; fieldName: 
Str255): Handle;    Asm #20

Return a handle to a zero-terminated string containing the value of 
field fieldName on the current card. You must dispose the handle.  
cardFieldFlag set to false indicates background, instead of card, 
field.

FUNCTION GetFieldByNum(cardFieldFlag: BOOLEAN; fieldNum: 
INTEGER): Handle;   Asm #21

Return a handle to a zero-terminated string containing the value of 
field fieldNum on the current card. You must dispose the handle.

FUNCTION GetFieldByID(cardFieldFlag: BOOLEAN; fieldID: INTEGER): 
Handle;   Asm #22

Return a handle to a zero-terminated string containing the value of the 
field while ID is fieldID. You must dispose the handle.
PROCEDURE SetFieldByName(cardFieldFlag: BOOLEAN; fieldName: 
Str255; fieldVal: Handle);    Asm #23

Set the value of field fieldName to be the zero-terminated string in 
fieldVal. The contents of the Handle are copied, so you must still 
dispose it afterwards.

PROCEDURE SetFieldByNum(cardFieldFlag: BOOLEAN; fieldNum: 
INTEGER; fieldVal: Handle);   Asm #24

Set the value of field fieldNum to be the zero-terminated string in 
fieldVal. The contents of the Handle are copied, so you must still 
dispose it afterwards.

PROCEDURE SetFieldByID(cardFieldFlag:BOOLEAN; fieldID: INTEGER; 
fieldVal: Handle);  Asm #25

Set the value of the field whose ID is fieldID to be the zero-
terminated string in fieldVal. The contents of the Handle are copied, 
so you must still dispose it afterwards.

FUNCTION StringEqual(str1,str2: Str255): BOOLEAN; Asm #26

Return true if the two strings have the same characters. Case 
insensitive compare of the strings.

PROCEDURE ReturnToPas(zeroStr: Ptr; VAR pasStr: Str255); Asm #27

zeroStr points into a zero-terminated string. Collect the characters 
from there to the next carriage Return or the end of the string, and 
return them in the Pascal string pasStr.

PROCEDURE ScanToReturn(VAR scanPtr: Ptr);    Asm #28

Move the pointer scanPtr along a zero-terminated string until it points 
at a Return character or a zero byte.

PROCEDURE ScanToZero(VAR scanPtr: Ptr); Asm #29

Move the pointer scanPtr along a zero-terminated string until it points 
at a zero byte.

FUNCTION GSToPString(src: GSString255Hndl): Str255;    Asm #30

Convert a GS/OS Class 1 input string (length word + text) into a pascal 
string.  If the source string has more than 255 characters, only the 
first 255 will be copied.

FUNCTION PToGSString(src: Str255): GSString255Hndl;    Asm #31

Convert a Pascal string to a GS/OS Class 1 input string (length word + 
text).  If the source string is empty, NIL is returned; otherwise a new 
handle is created and filled with the text.

FUNCTION CopyGSString(src: GSString255Hndl): GSString255Hndl;
     Asm #32
Simply copies a GS/OS Class 1 input string (length word + text).  If 
the handle is NIL or empty or contains a length word of zero, NIL is 
returned, otherwise a new handle is created with an exact duplicate of 
the input text.

FUNCTION GSConcat(src1, src2: GSString255Hndl): GSString255Hndl;
     Asm #33
Concatenates two GS/OS Class 1 input strings (length word + text).  If 
both inputs are NIL or empty or contain a length word of zero, NIL is 
returned, otherwise a new handle is created with the inputs placed end-
to-end.

FUNCTION GSStringEqual(src1, src2: GSString255Hndl): BOOLEAN;
     Asm #34
Performs a case-insensitive comparison of two GS/OS Class 1 input 
strings (length word + text).  If the strings are equal, TRUE is 
returned.  NIL or empty handles are considered to have length of zero, 
and two strings of length zero will be judged equal.

FUNCTION GSToZero(src: GSString255Hndl): Handle;  Asm #35

Converts a GS/OS Class 1 input string (length word + text) into a zero-
terminated string.   Even if the input string is empty (NIL or empty 
handle or length = 0) a zero handle will be created- an empty zero 
handle has length 1 and contains only a zero terminator.

FUNCTION ZeroToGS(src: Handle): GSString255Hndl;  Asm #36

Converts a zero handle into a GS/OS Class 1 input string (length word + 
text).  If the source handle is empty or has length zero, NIL is 
returned, otherwise a new handle is created, the length word is set, 
and the text is copied into it.

The next four calls are designed to assist XCMD's which use resources.  
The IIGS Resource Manager prior to System 6.0 does not support "named" 
resources, but there is a standard format for storing names of 
resources in "name resources".  These callbacks provide a simple 
library of routines to assist identifying resources by name.  See the 
IIGS ToolBox Reference, vol. 3, for more information.

FUNCTION LoadNamedResource(whichType: ResType; name: Str255): Handle;
     Asm #37

Searches for the resource of this type and name and loads it.  If the 
resource is not found, returns NIL.  Respects the current resource 
search path:  Starts from the current file, and only goes as deep as 
the specified depth.

FUNCTION FindNamedResource(whichType: ResType; name: Str255; VAR 
homeFile: INTEGER; VAR id: ResID): BOOLEAN;  Asm #38

Searches for the resource of this type and name. If the specified 
resource is found, returns TRUE, and file and id are set to the 
containing file and the resource ID.  If the resource is not found, 
returns FALSE, and the file and ID are undefined.  Respects the current 
resource search path:  Starts from the current file, and only goes as 
deep as the specified depth.  Note:  It is important to take note of 
the file number, because it would be possible to have two resources, in 
different files, with the same ID, and different names:  If you asked 
for the deeper of the two, and then attempted to load it by ID, you 
would get the shallower one.

PROCEDURE SetResourceName(whichType: ResType; id: ResID; name: 
Str255);  Asm #39

Changes the name of a given resource to "name".  Respects the current 
resource search path:  Starts from the current file, and only goes as 
deep as the specified depth.  If no resource can be found within the 
search criteria, nothing will happen.  If you set a resource name to 
the empty string, it has no name, and may then only be referred to by 
type and ID.

The IIGS resource manager does not directly support named resources.  
One effect of this is that you can delete resources, but the names 
remain.  Therefore, when deleting a resource, named or not, it is a 
good idea to call this routine and set the resource's name to the empty 
string - thus clearing out the name.

This routine will not add a name unless there is a corresponding target 
resource of the given type and ID.  If you are adding a new resource, 
then, you must first add the resource, and then you may give it a name.

FUNCTION GetResourceName(whichType: ResType; id: ResID): Str255; Asm 
#40

Returns the name of a given resource.  Respects the current resource 
search path:  Starts from the current file, and only goes as deep as 
the specified depth.  If the resource has no name, or if the resource 
doesn't exist, you will get an empty string in both cases.  Thus, you 
must use different means to determine the existence of the resource 
itself.  This call only identifies the existence of a name - not the 
resource itself.

The next two calls  are designed to assist XCMD's which wish to use the 
IIGS sound hardware (referred to herein as the "DOC chip").  These 
callbacks provide a very simple form of arbitrarion, by allowing the 
XCMD's to reserve the DOC for their own use.  If you leave the XSound 
activated, HyperCard IIGS will never be able to make another sound 
(until re-launched) so if you must use these callbacks, PLEASE be sure 
to call EndXSound at an appropriate time.  It is not automatic.

PROCEDURE BeginXSound;   Asm #41
 
Indicates to HyperCard IIGS that an XCMD would like to take over 
control of the DOC chip.  After this call, HyperCard IIGS will never 
emit sound from the speaker.  The play command will simply fall 
through.  You MUST execute the EndXSound call for HyperCard IIGS to 
make any more sounds.

PROCEDURE EndXSound;     Asm #42

Indicates to HyperCard IIGS that an XCMD has completed usage of the DOC 
chip.  After this call is made,  HyperCard IIGS may resume its own use 
of the DOC chip, and may begin making sounds.

The next two calls  are designed to assist XCMD's and which wish to do 
image processing.  They provide an easy way to get images in and out of 
HyperCard IIGS.  This could be used in conjunction with a scanner or 
digitized XCMD, for example.  DO NOT use these calls during painting, 
because there are a number of other buffers which are also involved.

PROCEDURE GetMaskAndData(VAR mask: LocInfo; VAR data: LocInfo);
     Asm #43

 
This call actually does two things:  first, HyperCard IIGS will update 
its internal buffers to contain the up-to-date image;  second, it will 
return the LocInfo records of the two image buffers.  The data buffer 
is a card-sized buffer which contains the image of "the card".  The 
mask buffer is a similar sized buffer which contains the "opaque" layer 
of the card.  In the opaque layer, 0 = transparent and 1 = opaque.  
Note:  ANY activities subsequent to this call can cause these buffers 
to change, so an xcmd must finish using the buffers, or at least make 
local copies, before making any of the callbacks which might update the 
screen, change cards, etc.

PROCEDURE ChangedMaskAndData(whatChanged: INTEGER);    Asm #44

This callback is used by XCMDs which actually change the picture on a 
card and wish it to be saved in the stack.  An XCMD which wants to 
change a picture should contain the following sequence of operations:
    GetMaskAndData;
    MakeChanges;
    ChangedMaskData(whatChanged);

The change codes are one of the following:

Code Meaning
0    No changes were made to the buffers, but please update the 
screen.  This is a good way to update the card window if an XCMD has 
been drawing in it.
1    The buffers were modified, but the XCMD does not wish to save the 
changes.  Please restore them to their original state.
2    New data and mask have been placed in the buffers;  or, new data 
was supplied and use the original mask.
3    New data was written, and this new image requires a mask buffer 
of "all transparent".  HyperCard IIGS will fill the mask with zeros 
before saving the pictures.
4    New data was written, and this new image requires a mask buffer 
of "all opaque".  HyperCard IIGS will fill the mask with ones before 
saving the pictures.
5    New data was written.  HyperCard IIGS will compute a new mask 
buffer using the following algorithm:  all white data pixels become 
transparent;  all non-white data pixels become opaque.

The callbacks listed below are new to HyperCard IIGS version 1.1.  
Attempting to execute these callbacks with a version of HyperCard IIGS 
prior to 1.1 will cause HyperCard to crash.  Care must be taken that 
the correct version of HyperCard is in use before using these 
callbacks.  Sample Pascal source is included below to illustrate a 
means of checking the version from an XCMD.

FUNCTION CorrectVersion: BOOLEAN;
{ This returns true if the version of HyperCard is >= minVersion }
     CONST
          minVersion = '1.1';
     VAR
          tempHandle:    Handle;
          tempStr:  Str255;
     BEGIN
          tempHandle := EvalExpr('the version');
          ZeroToPas(tempHandle^, tempStr);
          IF temphandle <> NIL THEN DisposeHandle(tempHandle);
          CorrectVersion := tempStr >= minVersion;
     END; {CorrectVersion}


PROCEDURE PointToStr(pt: Point; VAR str: Str255); Asm #45

This callback converts a point to a Pascal string.  This is typically 
used when an XCMD needs to return a point to HyperCard as an interim 
measure before calling PasToZero to convert the new Pascal string to a 
zero-terminated string.

PROCEDURE RectToStr(rct: Rect; VAR str: Str255);  Asm #46

Similar to PointToStr, this callback converts a rect to a Pascal 
string.

PROCEDURE StrToPoint(str: Str255; VAR pt: Point); Asm #47

The compliment of PointToStr, this callback converts a Pascal string to 
a point.  Missing coordinates will be converted to zero.  For example 
the string "5,15" will be converted to a point with the horizontal 
coordinate being five and the vertical coordinate being 15 while the 
string "8" would result in a point with a horizontal coordinate of 
eight and a vertical coordinate of zero.

PROCEDURE StrToRect(str: Str255; VAR pt: Point);  Asm #48

Similar to StrToPoint, this callback converts a string to a rect.

FUNCTION NewXWindow(boundsRect: Rect; title: Str31; 
          visible: BOOLEAN; wStyle: INTEGER): WindowPtr;
                    Asm #49

This callback creates an XWindow.  An XWindow is very similar to a 
standard with several key exceptions noted below.  BoundsRect defines 
the location and size of the content region of the window.  Note that 
the actual size of the window frame depends upon the window style.  
Title is a Pascal string of up to 31 characters which is used to 
reference the window by name from HyperTalk.  The visible parameter 
determines indicates whether the window will be created initially 
visible.  WStyle is one of the following constants declared in the 
HyperXCMD interface file.

     xWindoidStyle  0    Window has a grey drag bar and close box.
     xRectStyle     1    A simple rectangle outlines the this style window.
     xShadowStyle   2    A rectangle with a drop shadow on the bottom and right sides.
     xDialogStyle   3    Identical in appearance to a dialog box.

As noted above, XWindows are very similar to standard windows obtained 
via the NewWindow toolbox call.  The differences include:  (1)  The 
programmer must never alter the wRefCon field of the window record.  
Instead, use the SetXWindowValue callback if you wish to save a value 
along with the window.  (2) The wFrameBits is inaccessible to the user 
and must not be changed.  (3) The programmer should not call 
CloseWindow on this window, instead, they must use the CloseXWindow 
callback which will properly dispose of additional memory HyperCard has 
allocated for the window and send the proper close event to the XCMD.

An XCMD that creates an XWindow has several additional 
responsibilities.  These include responding to events specific to the 
XWindow(s) it creates as well as disposing of any additional memory the 
XCMD may have allocated when creating the XWindow.  See the section 
"Care and Feeding of Your New XWindow" below for complete details on 
responding to XWindow events.

PROCEDURE SetXWIdleTime(window: WindowPtr; interval: LongInt);
                    Asm #50

This callback instructs HyperCard how often (in ticks) the XCMD would 
like to receive null events for each XWindow it has created.  The 
default is zero which indicates the XCMD does _not_ wish to receive 
null events.  Care should be taken that an XWindow does not slow down 
HyperCard unnecessarily by receiving more null events than it 
absolutely requires.  A clock XWindow, for instance, would most likely 
not need a null event more often than once per second at the most.

PROCEDURE CloseXWindow(window: WindowPtr);   Asm #51

This callback sends a closeEvent to the owner of the indicated XWindow, 
releases all memory HyperCard has allocated for internal use regarding 
the XWindow, and closes the window via a CloseWindow call.  
Additionally, if the owner XCMD of the window being closed no longer 
owns any windows, its code is disposed of and its memory ID released if 
no calls are currently pending for it or marked for later disposal if 
calls are currently pending.  The XCMD may save itself from being 
disposed if it opens additional windows after it is marked for disposal 
but still awaiting pending or recursive calls.

PROCEDURE HideHCPalettes;          Asm #52

This callback hides all currently open HyperCard windows including all 
XWindows and all built-in windows except for the card window.  
Additionally, it records the visible state of each window when the call 
was executed so that a subsequent call to ShowHCPalettes can restore 
all windows that were visible.  The xHidePalettesEvt XWindow message is 
sent to every XWindow when this call is executed.  This callback is 
typically used when it is necessary to clear the screen of 
obstructions, such as when using the Apple II Video Overlay Card.

PROCEDURE ShowHCPalettes;          Asm #53

The compliment to HideHCPalettes, this callback shows all windows that 
were visible at the time of the HideHCPalettes call and sends the 
xShowPalettesEvt to every XWindow.

FUNCTION GetXWindowValue(window: WindowPtr): LongInt;  Asm #55

This callback retrieves the value of an XWindow previously set by the 
SetXWindowValue callback below.  This value is inistailized to zero 
when the XWindow is created.

PROCEDURE SetXWindowValue(window: WindowPtr; customValue:   
     LongInt); Asm #54

This callback allows the XCMD owner of an XWindow to save a LongInt 
(four byte) sized value along with an XWindow it creates.  Since an 
XCMD may own multiple XWindows, this callback allows the XCMD to save a 
unique value along with specific windows.  Typically, this value would 
contain a picture handle for updates or a handle to a record containing 
data needed for the particular XWindow.


GETTING STARTED

It is strongly suggested that the XCMD author start by copying the 
given sample XCMDs and modifying them.  The code itself is quite 
simple, however the steps to build an XCMD are somewhat complex.  The 
examples given are ABeep, PBeep and CBeep.  They each create a command 
which simply beeps the speaker a given number of times.  Each version 
comes in its own folder with a make file, program source, and a rez 
source file.  These samples are included in MPW IIGS and APW format.

To make an XCMD, set the MPW directory to that folder, and select 
Build.... from the build menu.  Type the XCMD name into the dialog, and 
the build process should begin.  The final step in a successful build 
is to copy the file containing the resource onto a ProDOS diskette.  
Now you must use the supplied RMover utility.  This is a simple 
resource mover with which you may add the new resource to an existing 
stack file.  When you use Rmover, be sure to click the "Types" button 
and select type $801E - XCMD, OMF.

After you place the XCMD in a stack, launch HyperCard IIGS and open the 
stack containing the XCMD.  To invoke the XCMD, simply type it's name 
and any required parameters into the message box.  From here on out, 
it's welcome to the wonderful world of debugging.



To create a new XCMD, copy the entire folder and rename every 
occurrence you can find of the resource name, to the new name.  That 
is, rename every file, -and- open the text files and rename every 
occurrence inside.


NOT FOR THE FAINT OF HEART

If you really want to start messing around, I urge you to read and 
completely understand the link commands and the rez source.  Many of 
the high-level compilers especially like to create lots of segments, 
and the makefile goes to great lengths to create a file which has (1) a 
single segment and (2) an entry point at the 1st byte in the segment.


CARE AND FEEDING OF YOUR NEW XWINDOW
External commands that create XWindows are kept in memory after they 
terminate so that they may be called in response to events concerning 
the XWindows they created.  An XCMD can determine whether it has been 
called from a script or the message box or in response to an event by 
checking the paramCount field of the parameter block.  If this value is 
negative, the XCMD has been called in response to an event.  The 
following Pascal code fragment illustrates this concept.

     BEGIN {SampleXCMD}
          { If the paramCount is negative, we have been called 
in
            response to an event }
          IF paramPtr^.paramCount < 0 THEN BEGIN
               HandleEvents;
               EXIT(SampleXCMD);
          END; {if}

          { program continues... }

The Events an External Command Will Receive
Upon calling an XCMD in response to an event, the following conditions 
are true:
     
     (1)  The current port is set to the XWindow which the 
event deals with.
     (2)  The memory ID field of the parameter block is the 
same as when the
          XCMD initially created the XWindow.
     (3) The first parameter is a pointer to an XWEventInfoPtr

The structure of an XWEventInfoPtr is as follows:

     eventWindow: WindowPtr;
     event:    EventRecord;
     eventParams: ARRAY[1..9] OF LongInt;
     eventResult: Handle;
     
The following is a list of all events an external command can receive 
along with a description of the event.  Some of the events are standard 
system events while others are generated by HyperCard.  In all cases 
the event record consists of the following fields:

     what:          INTEGER;  { event code }
     message:  Longint;  { event message }
     when:     Longint;  { ticks since startup }
     wher :    Point;    { mouse location }
     modifiers:     INTEGER;  { modifier flags }


HyperCard Event - "xOpenEvt"
xOpenEvt is always the first event sent to any XWindow.  This event is 
sent to any new XWindows immediately after the parent XCMD terminates.  
No events will be sent to an XWindow before this event.

HyperCard Event - "xCloseEvt"
This is HyperCard's method of notifying the XCMD that a window that it 
created is being closed.  At the time of the event, the window is still 
present and visible.  The window will be closed immediately following 
the event.  The owner XCMD should not call CloseXWindow or close the 
window in any other means when it receives this event.  HyperCard will 
handle closing the window when the XCMD terminates.  XCMDs will 
typically use this event to dispose of any additional memory they have 
allocated for the XWindow.

HyperCard Event - "xHidePalettesEvt"
This event is sent to all XWindows when an external command executes 
the HideHCPalettes callback.  An external command may wish to 
deallocate memory or take other action when the user hides their window 
in this manner.

HyperCard Event - "xShowPalettesEvt"
This event is sent to all XWindows when an external command executes 
the ShowHCPalettes callback.  An external command may need to prepare 
itself to handle update events, etc, if it has deallocated the memory 
when being hidden.

HyperCard Event - "xCursorWithin"
This event is sent to an XWindow when the mousecursor enters therect of 
their window.  The owner XCMD can then set the cursor to a custom shape 
if desired using the toolbox.  If they do not wish to handle the 
changing of the cursor shape, the external command should set the 
passFlag of the parameter block to TRUE so that HyperCard will handle 
the cursor itself.  Since this message is sent repeatedly, as long as 
the cursor is within the window, the XCMD should determine whether it 
has already changed the cursor shape before doing so again to avoid 
flickering of the cursor and slowing down the CPU. 

System Event - "UpdateEvt"
This event indicates the all or part of the specified window needs to 
be updated.  It is the responsibilty of the owner external of each 
window to handle redrawing the contents of the window in response to 
this event.

System Event - "MouseDownEvt"
The user has pressed the mouse button while the mouse pointer was 
within an external window.  The external command will commonly handle 
tracking the mouse click manually, or call FindControl and have the 
control manager do so.

System Event - "NullEvt"
This event will only be sent if the XCMD/XFCN has enabled idle events 
by setting the idle interval to a value other than zero with the 
SetXWIdleTime callback.  See the description of this callback above for 
more information regarding using null events.


NOTES ABOUT XCMDS THAT OPEN XWINDOWS
An XCMD that creates an XWindow is treated differently from an XCMD 
that doesn't.  The following section details differences the XCMD 
author needs to be aware of.

1)  XCMDs that create external windows are kept in memory as long as 
any XWindows that XCMD created are open.
2)  XWindow XCMDs should not assume that they own only one window.  
Subsequent calls to the XCMD may create additional windows.  The XCMD 
should use the windowPtr passed and the GetXWindowValue and 
SetXWindowValue calls to determine the appropriate action to take in 
response to a particular event.
3)  The same Memory Manager user ID is passed to an XWindow owner XCMD 
as long as any of the XWindows the XCMD owns are open.  The XCMD should 
not perform a DisposeAll on its memory ID because other windows that 
the XCMD is responsible for are still open. 
4)  The wFrameBits field of an XWindow is inaccessible to an XCMD, 
XCMDs should not attempt to modify the standard window attributes of an 
XWindow.
5)  XWindows may remain open throughout various stacks.  Any resources 
needed by a particular XWindow should be detached and maintained by the 
XCMD when the window is opened.
6)  XCMDs that have currently open XWindows are inserted into the 
heirarchy immediately after the stack script of the current stack.  
XCMDs that open XWindows should be aware that they may be called from 
HyperTalk after leaving their parent stack.