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.