XControl - Extensible Control Panel
	   for ST/MEGA/STe/TT Computers

SPECIFICATIONS SUBJECT TO CHANGE WITHOUT NOTICE
XControl Version: 1.0
This document is Copyright (c) 1990, Atari Corporation

OVERVIEW

This document describes a new Control Panel ( XControl) for ST/MEGA/STe/TT
computers, which features loadable Control Panel extensions (CPXs) that
performs various sytem configuration functions.  XControl, with its
loadable modules, gives the Control Panel the advantages of any software
with modular design: ease of maintenance and expandability.  Improvements
to any part of XControl can be distributed individually, by distributing
CPX updates, without the need for updating all parts of XControl.  This
scheme is more flexible for users, since XControl will only load the CPXs
which a user needs.  Software vendors can create and distribute their own
CPXs to extend the functionality of XControl beyond what Atari provides,
or to provide graphical front ends for their TSR utilities.


HOW XCONTROL WORKS

XControl is the combination of a master desk accessory, which loads the
various CPXs and manages user selection of CPXs, and the extensions 
themselves, which perform the various system configuration tasks.

When XControl is loaded, at boot time, it looks for a a file called
CONTROL.INF in the root of the boot device.  CONTROL.INF contains the 
default settings for XControl. 

One of the settings is the CPX DIRECTORY PATH.  This is the directory 
where XControl looks for CPXs.  If XControl finds the folder, it reads the 
header of each .CPX file that it finds there.  The  header's id number and 
version number are compared to those already loaded.  The end result is 
that only the latest version of a CPX will be retained.

If the header indicates that a .CPX needs to be run at boot time, XControl 
loads the CPX and calls its initialization hook.  If the CPX header also 
indicates that the user prefers it to be memory resident, XControl keeps 
the CPX in memory.

After checking CPXs and initializing those that need it, XControl adds an 
additional set of CPX slots.  The number of slots available is based upon 
either the default minimum number of slots set by the user, or 1-1/2 times 
the number of CPXs loaded, whichever is greater.  Additional CPXs may be 
loaded later during a RELOAD command.  All CPXs are initialized as if they 
were new, except that existing 'resident' CPXs are retained.  New CPXs 
marked 'resident' are treated as non-resident CPXS after a reload.  During 
a reload, a new CPX will only be loaded if there is a vacant slot available.

After adding additional vacant slots, XControl waits like any other desk 
accessory for an AC_OPEN message.

When selected from the Desk menu, XControl opens a window and displays a 
menu of active CPXs.  When a CPX is chosen from the menu,  XControl 
attempts to invoke it via the XControl <-> CPX software interface described 
below.  Resident CPXs are invoked immediately;  non-resident CPXs are loaded 
from the CPX storage directory.  If the CPX is not found by name, XControl 
looks at all other CPXs in that directory, comparing  id numbers and version 
numbers.  If an exact match is found,  that CPX is invoked instead.  
Otherwise, a file-not-found alert is displayed.

When invoked, the CPX assumes control of the work area of the XControl 
window, and can present its own interface there.  XControl dispatches user 
events through the CPX, but handles most of the window related events 
itself.  XControl also provides a number of utility routines to CPXs, 
including an extended form_do call which CPXs can use to simply handle 
dialog-style interfaces within the XControl window.

It's expected that most CPXs will use this extended form_do() software 
interface so that the user can move or close the XControl window at will.  
Each CPX should provide at least an OK and Cancel button so that the user 
can return to the XControl master from the CPX.  Each CPX must also be able 
to respond to an Abort signal from XControl, so that the user can dismiss 
XControl with the close box, and so that XControl can clean up if it is 
active while the main application is terminated.  When the user exits a 
non-resident CPX, it is unloaded, and the memory that it took up is 
recovered by the system. 


CONTROL PANEL EXTENSIONS

The concept of what constitutes a CPX is very important to the 
implementation of the extensible Control Panel.  A CPX is effectively
a subroutine call.  It is neither an application nor a desk accessory,
but only a means for setting system parameters.  Examples of CPX functions
include:

  -  Color Selection
  -  Keyboard/Mouse configuration ( repeat rate, audible keyclick, etc. )
  -  RS232 port configuration
  -  Printer configuration

Note the key word "configuration" in most of the above functions.  
A printer driver does not belong in a CPX, but the ability to configure a 
TSR printer driver would be a good thing to have in a CPX.  The key concept 
to keep in mind here is that of the "Control Panel" - the one place where a 
user goes to toggle switches, press buttons, or whatever, to "control" the 
functions of the computer.  Obviously, it is silly to have a CPX which 
controls the operation of a desk accessory, Instead, CPXs should primarily 
be used as graphical front ends for TSR utilities.


XCONTROL <-> CPX SOFTWARE INTERFACE

When XControl first starts up, it loads the headers of all the CPXs that it
can find.  At boot time, it initializes each CPX which has the bootinit 
flag set in its header by jsr'ing to the start of the CPX's text segment.
This in turn will jmp  to the cpx_init() function.  This function returns a
pointer to a structure containing information about the CPX, including 
pointers to  routines which XControl uses to invoke CPX functions.

"Set-only" CPXs may be implemented.  They should set whatever is needed
during the cpx_init() call and return NULL.  If a CPX is set for bootinit,
XControl also checks the 'Set-Only' flag in the header to determine if the
CPX is Set-only.  XControl will only execute Set-only CPXs at boot time and
at reloads.  They will not appear on the XControl main menu, and thus will
never again be called after the cpx_init() call.  

XControl uses an event_multi() for its user interaction.  When a CPX is
chosen by the user, XControl loads the CPX into memory and calls cpx_init()
again, this time, with the 'booting' parameter set to FALSE.  XControl then
invokes the cpx_call() routine to begin CPX interaction.  The cpx_call()
routine should first initialize the CPX interface.  It may then handle the
user interface via the extended form_do call and return FALSE to exit the
CPX ( See Form CPXs ), or to return TRUE and allow XControl to manage the 
user interace by dispatching evnt_multi() events through the CPX event 
handling routines. ( See Event CPXs ).



FORM CPXs versus EVENT CPXs

FORM CPXs are those which use Xform_do() to handle user interaction with a 
standard AES form.  XControl handles window movements and redraws.  To the 
CPX, it looks just like the old familiar form_do() with a few extensions:

	-	Keys other than those which work in editable text fields
	 	can be handled by  the CPX.
	-	Special redraws may be done by the CPX.
	-	If the user closes the XCONTROL window,k or quits the
		parent application, the CPX is informed so that it may
		clean up.  AC_CLOSE should be treated as "Cancel",
		and WM_CLOSE as "OK".

To give you an idea of the flexibitlity FORM CPXs may have, all of the
CPX's released with XControl 1.0 are FORM CPXs.

EVENT CPXs are those which use XControl to dispatch AES events directly, 
for maximum flexibility.  These CPXs give XControl a list of event handlers
in the CPXINFO struct returned by cpx_init().  When an Event CPX is called,
it tells XControl which events it cares about via the set_event_mask()
function, then it returns to XControl and waits for tis event handlers to 
be called.  Event CPXs exit by setting a flag passed to the event handlers.

Because of the flexibility offered by Xform_do(),  and the more complex 
nature of the event handlers, it's generally much easier to write a Form 
CPX than an Event one.  The main reason you might want an Event CPX is to 
handle timer events which are not supported by XForm_do().


XCONTROL ROUTINES

At boot-time or invocation time, XControl jsr's to the text segment of the
CPX.  XControl passes on the stack a pointer to an XControl Parameter Block,
with information of interest to the CPX.  The XCPB struct looks like:

	typedef struct {
	short	handle;	From XControl's Graf_Handle() Call.
			See the Notes on workstations below.

	short	booting;	Non-zero if this cpx_init() call is
			 	part of XControl's initialization.
			 	
	short	reserved;
	short	SkipRsh Fix;	The cpx must call the resource fixup routine
			 	only once.  Non-zero means skip the fixup.
	void	*reserve1;
	void	*reserve2;
	
	void	(*rsh_fix)( int num_objs, int num_frstr, int num_frimg,
			    int num_tree, OBJECT *rs_object,
			    TEDINFO *rs_tedinfo, BYTE *rs_strings[], 
			    ICONBLK *rs_iconblk, BITBLK *rs_bitblk,
			    long *rs_frstr, long *rs_frimg, long *rs_trindex,
			    struct foobar *rs_imdope );

	void	(*rsh_obfix)( OBJECT *tree, int curob );

	short	(*Popup)( char *items[], int num_items, int default_item,
			  int font_size, GRECT *button, GRECT *world );

	void	(*Sl_size)( OBJECT *tree, int base, int slider, int num_items,
			    int visible, int direction, int min_size );

	void	( *Sl_x) ( OBJECT *tree, int base, int slider, int value,
			   int num_min, int num_max, void (*foo)() );

	void	(*Sl_y)( OBJECT *tree, int base, int slider,  int value,
			 int num_min, int num_max, void (*foo)() );

	void	(*Sl_arrow)( OBJECT *tree, int base, int slider, int obj,
			     int inc, int min, int max, int *numvar,
			     int direction, void (*foo)() );

	void	(*Sl_dragx)( OBJECT *tree, int base, int slider,
			     int min, int max, int *numvar, void (*foo)() );

	void	(*Sl_dragy)( OBJECT *tree, int base, int slider, 
			     int min, int max, int *numvar, void (*foo)() );

	WORD	(*Xform_do)( Object *tree, WORD start_field, WORD puntmsg[] );

	GRECT	*(*GetFirstRect)( GRECT *prect );

	GRECT	*(*GetNextRext)( void );

	void	(*Set_Evnt_Mask)( int mask, MOBLK *m1, MOBLK *m2, long time );

	BOOLEAN  (*XGen_Alert)( int id );

	BOOLEAN  (*CPX_Save)( void *ptr, long num );

	void	*(*Get_Buffer)( void );

	int	(*getcookie)( long cookie, long *p_value );

	int	Country_Code;	Contains the Country Code that the Control Panel
				was compiled for. For a list of the Current
				Country Codes, please see the Rainbow TOS
				Release Notes - BIOS/XBIOS Supplemental
				Documentation, page 63.

	void	MFsave(  BOOLEAN saveit, MFORM *mf );
	} XCPB;


RESOURCE MANAGEMENT:

Resource Object Tree Fixup Function:

Rsh_fix() fixes up the CPX object tree based upon 8x16 pixel characters.  
This ensures that the CPX will be the same size in all resolutions.  
The CPX object tree should be a 'Panel', not a 'Dialog'.  It should be 
created in ST HIGH resolution.  In comparison, the 'rsrc_load()' function 
would fixup the tree based upon the current character width and height for
that resolution.  This is why panels can appear stretched or scrunched in 
different resolutions when using 'rsrc_load()'.

The CPX should only call rsh_fix() when the XControl Parameter Block 
'SkipRshFix' flag is ZERO.   The reason for this is because  a resource 
should only be converted to pixels ONCE.  

	void	(*rsh_fix)( int num_objs, int num_frstr, int num_frimg,
			    int num_tree, OBJECT *rs_object,
			    TEDINFO *rs_tedinfo, BYTE *rs_strings[], 
			    ICONBLK *rs_iconblk, BITBLK *rs_bitblk,
			    long *rs_frstr, long *rs_frimg, long *rs_trindex,
			    struct foobar *rs_imdope );

	IN:   All of the input variables can be found in the CPX RSH file.
	OUT: None


Resource Object Fixup Function:

Call this function ONLY when you want to convert a specific object to pixel
format AND when the object is still CHARACTER based.  The only reason you 
would need to call this function would be if you were doing your own 
resource fixup for a resource that was not created by the Atari RCS.

	void	(*rsh_obfix)( OBJECT *tree, int curob );

	IN:	OBJECT *tree	The object tree of the CPX
		int curob	The resource object to convert

	OUT:	None


POP UP MANAGEMENT:

Call this function to have the CPX display a popup box:

	short	(*Popup)( char *items[], int num_items, int default_item,
			  int font_size, GRECT *button, GRECT *world );

	IN:	char 	*items[];	Pointer to an array of strings.
		int	num_items;	Number of items ( 1 based )
		int	default_item;	The default item ( zero based )
		int	font_size;	8x16 ( Large ) or 8x8 ( small ) Font
		GRECT 	*button;	GRECT of button pressed to invoke popup.
		GRECT 	*world;		GRECT of your tree.

	OUT:	short;         Returns  the item selected (zero based ) or -1

The string array passed to the popup routine must be properly padded by 
the calling cpx.  There must be at least 2 spaces in front of each string, 
and each string must be padded with spaces up to the length of the largest 
string plus 1.

The number of items listed versus the number of strings available is not 
checked.  If they do not correspond,  errors may occur.  In addition, if 
there are more than 5 items, only 3 will be displayed at any one time.  
The first position will display an up arrow, and the 5th position will 
display a down arrow.  Scrolling thru the popup will display the remaining
menu items, with a check mark indicating the default item.

Sometimes a default item is not necessary.  Setting the default_item = -1 
will prevent a check mark from appearing.

There are 2 font sizes available for AES objects the large and the small 
font.  Currently, the large font is always used and the height is assumed 
to be 16 pixels.

The GRECT of the button that activated the popup menu is required so that 
the menu is at least as wide as the button. 

The GRECT of the world is required so that if the popup menu exceeds the 
right edge, it pops left instead.  If the popup menu exceeds the bottom, 
it pops upwards.  In most cases, the "world" is the dimensions of your 
CPX's main form .

While the popup menu is displayed no other action other than popup menu 
manipulation is allowed.


SLIDER MANIPULATION:

Slider Size Adjustment:

This function is used to adjust the slider size within its base, so that 
the size of the slider represents the amount of data displayed, relative 
to the total amount of data.  In certain cases, it is best that the slider
not be sized.  An example of this is when the slider also contains a text
string.  It is possible, that if sized, the slider can no longer display 
the text string properly by either being too small or too large.

	void	(*Sl_size)( OBJECT *tree, int base, int slider, int num_items,
			    int visible, int direction, int min_size );

	IN:	OBJECT *tree;	The object tree:  ( OBJECT *)rs_trindex[ TREENAME].
		int base;	The base is the object of the sliders limits.
		int slider;	The object that moves within the limits defined by base.
				The slider must be the child of the base.
		int num_items;	The number of items (range)
		int visible;	The number of items visible
		int direction;	Horizontal or Vertical
		int min_size:	The minimum pixel size of the slider

	OUT:	none



Slider X/Y Functions:

Sl_x() and Sl_y() are used to update the position of the slider within its base.

	void	(*Sl_x)( OBJECT *tree, int base, int slider, int value, 
			 int num_min, int num_max, void (*foo)() );

	void	(*Sl_y)( OBJECT *tree, int base, int slider, int value,
			 int num_min, int num_max, void (*foo)() );


	IN:	OBJECT *tree;	( OBJECT *)rs_trindex[ TREENAME ];
		int base;	Base of the slider ( slider limits )
		int slider;	The object that will be moved around.
				This must be the child of the base.
		int value;	The NEW value related to the slider range.
				( Range: 0 - 1000 )
		int num_min;	The minimum number value can equal to.
		int num_man;	The maximum number value can equal to.
		(*foo)();	Pointer to a CPX defined function to update
				its items.  Set to NULLFUNC if there
				is no routine.

	OUT:	none




Slider Arrow Functions:

Call this when the user selects the arrows of a slider.  Direction is 
either Horizontal or Vertical.  Note that this is an ACTIVE slider where
objects are updated immediately, unlike the AES graf_slidebox where 
objects are updated only after the user lets go of button one. 

	void	(*Sl_arrow)( OBJECT *tree, int base, int slider, int obj,
			     int inc, int min, int max, int *numvar,
			     int direction, void (*foo)() );

	IN:	OBJECT *tree:	The resource tree
		int base:	The base of the slider ( slider limits )
		int slider:	The object that can be moved around
		int obj:	The arrow button object clicked on
		int inc:	The increment amount ( +/- # )
		int min:	The minimum value possible
		int max:	The maximum value possible
		int *numvar:	The current value
		int direction:	Horizontal or Vertical
		void (*foo)():	Pointer to a CPX defined function to
				update its items. Set to NULLFUNC if
				there is no routine.

	OUT:	none



Slider Paging Functions:

Paging is implemented by calling sl_arrow() with an increment/decrement 
value representing a "page" worth of data.  Paging is done when the user 
clicks on the base.  To implement paging the CPX can do this:

	MRETS mk;
	int  inc, ox, oy;

	Graf_mkstate( &mk );
	objc_offset( tree, slider, &ox, &oy );
	inc  = ( ( mk.y < oy ) ? ( -1 ) : ( 1 ) );  
	sl_arrow( fill in variables here );

This example is for vertical sliders and the increments were set to +/- 1.
Paging usually increments or decrements by the visible amount.  To do
horizontal pages, use the 'ox' and 'mk.x' variables instead and don't
forget to set the horizontal or vertical flag as necessary in sl_arrow().


Slider Drag Functions:

Called when the user 'drags' the slider around.  Again, this is an ACTIVE 
slider and will call Sl_x() or Sl_y() appropriately. 

	void	(*Sl_dragx)( OBJECT *tree, int base, int slider, int min,
			     int max, int *numvar, void (*foo)() );

	void	(*Sl_dragy)( OBJECT *tree, int base, int slider, int min,
			     int max, int *numvar, void (*foo)() );

	IN:	OBJECT *tree:	The resource tree
		int base:	The base of the slider ( slider limits )
		int slider:	The object that can be moved around
		int min:	The minimum value possible
		int max:	The maximum value possible
		int *numvar:	The current value
		void (*foo)():	Pointer to a CPX defined function to
				update its items. Set to NULLFUNC if
				there is no routine.

	OUT:	none



User Supplied Slider Update Function:

The User Supplied CPX function may be required so that the CPX can perform
operations specific to the active slider.  In most cases, this will simply 
be updating the text string and then performing a redraw.  In a more 
complicated setting, this can be anything from changing colors to 
performing a blit.  If no function is required, pass NULLFUNC instead.

The value that you can use to update the text string is contained in the
variable that you passed by reference into the calling slider function.
Whenever you call sl_arrow(), or sl_draw(), XControl updates that variable
just before calling sl_x() and sl_y().  These in turn will call your foo()
function.
 
The prototype for foo() is:	void (*foo)( void );


XFORM_DO FUNCTION:

XControl makes a custom form handler available to CPXs so that they may 
use the standard AES forms interface in a window, without worrying about 
handling window messages.  The object tree should fit within the standard 
control panel window ( 256x176  pixels work area ).  This restriction may 
be lifted in a future version.  The name of the routine is Xform_do and it 
functions like the built-in AES form_do routine with a few exceptions.  
One additional parameter is used, and a return value of -1 has a special 
meaning.

Timer Events are not supported under XForm_do().  If timer events are 
necessary, the CPX should be designed as an Event CPX.

If the CPX is looking for double clicks, the return value should be 
checked for -1 BEFORE checking for a double click. 

	WORD	(*Xform_do)( Object *tree, WORD start_field, WORD puntmsg[] );

	IN: 	OBJECT *tree;		Same as form_do;
	  	WORD start_field;	Same as form_do;
		WORD puntmsg[];		Defined as  WORD msg[8];

	OUT:	Same as form_do(): 	Returns the object number with the high
					bit set if a touch-exit was double
					clicked on.
	
		-However-

		if return is -1, this means that the CPX should look at the
		puntmsg[] array and treat it like the message array from an
		event multi.  The three messages to look for are:

		WM_REDRAW:	Sometimes the CPX needs to redraw items that
				are not part of the tree.  This is the time
				to do so.  XControl makes available
				GetFirstRect() and GetNextRect() so that the
				CPX can get the rectangle list and redraw
				accordingly.

		AC_CLOSE:
		WM_CLOSE:	When these messages are received, the CPX
				should immediately FREE any memory that
				it malloc'ed and return to XCONTROL by
				exiting cpx_call().  Do NOT leave any
				memory allocated, else fragmentation will
				occur. We strongly recommend that CPXs
				Do Not Malloc any memory.

		CT_KEY:		A key was pressed. puntmsg[3] contains the
				keycode of the key pressed as returned from
				an 'evnt_keybd()'.  Note that we return
				non-printable keys only, such as F1-F10,
				Help and Undo.  However, the 'Arrow' keys
				are not supported, because they are
				handled by Xform_do() for editable text fields.
				Note: CT_KEY == 53.
 
		IMPORTANT:	Always treat AC_CLOSE() as "Cancel" and
				treat WM_CLOSE() as an "OK".



GET FIRST/NEXT RECTANGLE LIST FUNCTIONS:

When redrawing the CPX due to a WM_REDRAW message, the CPX should use 
these routines to go down the rectangle list.  Since the Xform_do() routine
will handle resource object redraws, the CPX must handle non_resource 
objects.

		GRECT	*(*GetFirstRect)( GRECT *prect );

		GRECT	*(*GetNextRext)( void );


	IN:	GRECT *prect:	The GRECT of the dirtied area.

	OUT:	The intersecting GRECT that you should redraw or
		NULL if there are no more rectangles.



SET EVENT MASK
( Use only with Event CPXs )

Used to set XControl's Evnt_multi() function.  Messages will be dispatched
to the CPX thru procedure variables passed in.

	void	(*Set_Evnt_Mask)( int mask, MOBLK *m1, MOBLK *m2, long time );

	IN:	int mask:	Events to receive ( ie: MU_MESAG | MU_KEYBD )
		MOBLK *m1:	Mouse rect and direction number one.
		MOBLK *m2:	Mouse rect and direction number two.
		long time:	Time to wait for a timer event ( 1000 = 1 sec )
				Note that you must set the mask with MU_TIMER.

	OUT:	none


MOBLK is defined as:	typedef struct {
			int m_out;	Direction for evnt_multi() to look for.
			int m_x;	The x,y,w,h of the bounding rectangle.
			int m_y;
			int m_w;
			int m_h;
			} MOBLK;


XCONTROL ALERT BOX:

Use this function to display an XControl Alert Box.  The dialog box will 
be centered within the work area of the XControl window.   The Alerts 
available are:

	SAVE_DEFAULTS		0	Save Defaults?
	MEM_ERR			1	Memory Allocation Error
	FILE_ERR		2	File I/O Error
	FILE_NOT_FOUND		3	File Not Found Error

			
	BOOLEAN  (*XGen_Alert)( int id );

	IN:	int id:	The alert id number

	OUT:	BOOLEAN: TRUE - OK
			 FALSE - Cancel
			 Alerts with only one button always return TRUE.



CPX SAVE DEFAULTS:

XControl allows a CPX to write configuration data directly into its file.
XControl will write the number of bytes specified from *ptr to the data 
segment of the CPX.  If the CPX isn't found by name, XControl will search 
the CPX directory for another file with the same id number and version 
number.  If found, that CPX will become the active cpx.  If still not 
found, a file not found alert will be generated.  The standard GEMDOS 
error will also occur if the disk is write-protected.   The start of the 
DATA segment begins at the variable SAVE_VARS which is declared in the 
CPXSTART.S file.  The CPX designer must allocate the appropriate amount of 
DATA segment storage by editing CPXSTART.S.

During boot_time initialization, the CPX should read the defaults from the 
data segment and act accordingly.

Lastly, the CPX should treat a "SAVE" action as an 'OK', but do not exit the CPX.

	BOOLEAN  (*CPX_Save)( void *ptr, long num );

	IN:	void *ptr	Pointer to the data that needs to be stored.
		long num	Number of bytes to write to data segment of CPX.

	OUT:	BOOLEAN:	TRUE - OK
				FALSE - Error occurred

XCONTROL GET_BUFFER FUNCTION:

This call returns a pointer to the 64 byte buffer in each header which can 
be used by the CPX.  The buffer should be used by CPXs that rely upon 
write-only registers.  For example, the baud rate and flow control data 
cannot be read from the Rsconf() call. ( In TOS 1.4 and greater, the baud 
rate CAN be inquired. )  Since a CPX cannot be guaranteed to be in memory, 
a non-volatile storage location must be set aside to accomplish this.  The 
CPX can set the register, store the value in the buffer and when cpx_init()
is called again, the CPX can restore the data into its internal variables.

	void	*(*Get_Buffer)( void );

	IN:	none
	
	OUT:	(void *)	Returns a pointer to the CPX.
				The CPX should cast the pointer
				it's required format.	



CPX GET COOKIE FUNCTION:

Use this routine to look for a cookie.  Please see the Cookie Jar 
specifications for more details.  The parameters are exactly the same.

	int	(*get_cookie)( long cookie, long *p_value );

	IN:	long cookie:	Cookie that we are looking for.
		long *p_value:	Value of cookie goes here if the cookie is valid.

	OUT:	Zero if the cookie is not found
		Non-Zero if the 'cookie' is found and places its
		value in the longword pointed to by p_value.
		If p_value is NULL, it doesn't put the value
		anywhere, but still returns the error code.

A cookie can be a convenient marker for a TSR to indicate where a CPX can 
find the configuration data used by the TSR.  That's one of the reasons 
the cookie jar exists! Use it!


CPX SAVE/RESTORE MOUSE FORM

Use this routine to save/restore a mouse image to/from an MFORM structure.
This is useful when one needs to use a FLAT_HAND for example, and then 
must restore the mouse to its original shape.  This is required so that a
CPX doesn't wipe out a custom mouse form being used by an application when
the CPX is invoked.

	void	MFsave(  BOOLEAN saveit, MFORM *mf );

	IN:	BOOLEAN saveit	MFSAVE - Save Mouse Form
				MFRESTORE - Restore Mouse Form
		MFORM *mf	Mouse Form to store image in

	OUT:	none


CPX INFORMATION ROUTINES

INITIALIZATION:

This routine is called at boot time and also whenever the CPX is executed 
and should be used by the CPX to initialize global variables, etc..  
XControl passses on the stack a pointer to the XControl Parameter Block,
which was defined earlier.  Cpx_init() should return a POINTER to the 
following structure, or NULL if it is a "set_only" CPX:

	CPXINFO *cpx_init( XCPB *xcpb );


	typedef struct {
	BOOLEAN	(*cpx_call)( GRECT *work );
	void	(*cpx_draw)( GRECT *clip );
	void	(*cpx_wmove)( GRECT *work );
	void	(*cpx_timer)( int *event );
	void	(*cpx_key)( int kstate, int key, int *event );
	void	(*cpx_button)( MRETS *mrets, int nclicks, int *event );
	void	(*cpx_m1)( MRETS *mrets, int *event );
	void	(*cpx_m2)( MRETS *mrets, int *event );
	BOOLEAN	(*cpx_hook)( int event, int *msg, MRETS *mrets,
 			     int *key, int *nclicks);
	void	(*cpx_close)(  BOOLEAN flag );	
	}CPXINFO;
 
Most of these calls are not used when the CPX is an Xform_do type.  Those
routines not used should be set to NULL in cpx_init();


INVOCATION:

Called when a CPX is invoked AFTER the cpx_init() call has been completed.
The function is passed a rectangle describing the current work area of the
XControl window.  This allows a CPX to set up for user interaction and 
optionally call the custom xform_do() routine to handle its user interface.

	BOOLEAN	(*cpx_call)( GRECT *work );

	IN:	GRECT *work:	GRECT of XControl work window.

	OUT:	FALSE 	Return FALSE if the CPX is done.
		TRUE	Return TRUE to tell XControl to continue to
			dispatch events via the XControl CPXINFO
			routines.


EVENT HANDLING FUNCTIONS:

While an Event CPX is active, these are called in response to the 
appropriate events.  The events returned by XControl are defined by
the Set_Evnt_Mask() call in the XCPB.  The event mask may be changed
at any time while a CPX is active, and the new mask will be used for 
the next evnt multi.  Note that the routines are listed in the same 
order they will be called for multiple event returns from evnt_multi().
These routines should set the word pointed to by 'event' to TRUE( 1 ) 
to return control to XCONTROL and its main menu, or leave that word 
alone to continue with CPX interaction.  The *event variable is the event
mask and should be ignored otherwise.

Message events are handled by XControl, unless intercepted by cpx_evhook()
as described below.


WINDOW MANAGEMENT:

CPXINFO Redraw Event:

Called when a CPX is active and the XControl window needs to be redrawn. 
This call is required by a CPX that uses XControl to dispatch events 
( an Event CPX ).  The CPX should pass the dirty area to GetFirstRect()
and GetNextRect() in order to redraw using the rectangle list.

	void	(*cpx_draw)( GRECT *clip );

	IN:	GRECT *clip:	GRECT of the dirtied area.

	OUT:	none



CPXINFO Window Move Event:

Called when the user moves the XControl window, so that the CPX may fix
up its object tree as necessary.  GRECT contains the work window's new 
coordinates.  This call is required by a CPX that uses XControl to 
dispatch events ( ie: an Event CPX ).

	void	(*cpx_wmove)( GRECT *work );

	IN:	GRECT *work:	GRECT of the new window coordinates

	OUT:	none


TIMER EVENTS:

Called when a timer event occurs.  This call is required by a CPX that
uses XControl to dispatch events( ie: an Event CPX ).  The '*event' 
variable is used to tell XControl that this event has terminated the CPX.  
Set to '1' to terminate the CPX, else IGNORE it.  Note that timer events 
for Form CPXs are not supported.

	void	(*cpx_timer)( int *event );

	IN:	int *event:	Set to '1' if this event terminates the CPX.
				else ignore this variable.

	OUT:	none



KEYBOARD EVENTS:

Called when a keyboard event occurs.  This call is required by a CPX that 
uses XControl to dispatch events.  The '*event' variable should be set to 
'1' if this event has terminated the CPX, otherwise, ignore it.

	void	(*cpx_key)( int kstate, int key, int *event );

	IN:	int kstate:	The state of the Control, Alt and Shift Keys.
		int key:	The high byte contains the scan code of the key
				pressed, and the low byte contains the ASCII code,
				if any.
		int *event:	Set to '1' if this event terminates the CPX.
				Ignore this variable otherwise.

	OUT:	none



MOUSE BUTTON EVENTS:

Called when a mouse button event occurs.  This call is required by a CPX 
that uses XControl to dispatch events.  Set the '*event' variable to '1' 
if this event terminates the CPX, otherwise, ignore it.

	void	(*cpx_button)( MRETS *mrets, int nclicks, int *event );

	IN:	MRETS *mrets:	The mouse parameters returned by the event.
		int nclicks:	The number of button clicks for this event
		int *event:	Set to '1' if this event terminates the CPX.
				Otherwise, ignore it.
	
	OUT:	none

	MRETS is defined as:

		typedef struct {
		WORD	x;
		WORD	y;
		WORD	buttons;
		WORD	kstate;
		}MRETS;



MOUSE RECTANGLE EVENTS:

Called when a mouse event occurs.  This call is required by a CPX that 
uses XControl to dispatch events ( ie: an Event CPX ).  Set the '*event' 
variable  to '1' if this event terminates the CPX, otherwise ignore it.

	void	(*cpx_m1)( MRETS *mrets, int *event );

	void	(*cpx_m2)( MRETS *mrets, int *event );

	IN:	MRETS *mrets:	Mouse parameters returned by this event.
		int *event:	Set to '1' if this event terminates the CPX.
				Otherwise, ignore it.

	OUT:	none

	MRETS is defined as:

		typedef struct {
		WORD	x;
		WORD	y;
		WORD	buttons;
		WORD	kstate;
		}MRETS;



CPX EVENT PREEMPTION HOOK:

Cpx_hook() is called immediately after evnt_multi returns BEFORE the event
is handled by XControl.  This routine should not normally be required by a
CPX, but is included for flexibility.

	BOOLEAN	(*cpx_hook)( int event, int *msg, MRETS *mrets,
			     int *key, int *nclicks );

	IN:	int event:	The event mask.
		int *msg:	The AES event message buffer.
		MRETS *mrets:	mouse parameters for this event.
		int *key:	Key returned.
		int *nclicks:	Number of button clicks for this event.

	OUT:	TRUE 	Return ( non-zero ) to override default event
			handling.
		FALSE	Return ( zero ) to continue with event handling.


CPX TERMINATION FUNCTION:

This routine is called whenever an ACC_CLOSE or WM_CLOSE message is 
generated.  The CPX should immediately free up any allocated memory and 
return to XControl.  This routine is required for all CPXs that use 
XControl to generate events.  Failure to free allocated memory will result 
in a fragmented system.  Note that this is for an Event CPX only and is 
not necessary for Form CPXs.  IMPORTANT: Always treat ACC_CLOSE messages 
as 'Cancel' and WM_CLOSE messages as 'OK'.  In addition, CPXs should not 
malloc memory if at all possible.

	void	(*cpx_close)( BOOLEAN flag );

	IN:	TRUE  - WM_CLOSE Message
		FALSE - ACC_CLOSE Message

	OUT:	none



CPX FILE FORMAT:

A CPX file header looks like: ( 512 bytes - 0x200 hex )

	typedef struct _cpxhead {
	unsigned short magic;		Magic Number == 100
	struct {
		unsigned reserved :13;	Reserved for Expansion
		unsigned resident :1;	RAM Resident Flag
		unsigned bootinit :1;	Boot Initialization Flag
		unsigned setonly  :1;	Set Only CPX Flag
	} flags;

	long		cpx_id;		CPX ID value
	unsigned short	cpx_version;	CPX Version number
	char		i_text[14];	Icon Text
	unsigned short	sm_icon[48];	Icon bitmap - 32x24 pixels
	unsigned short	i_color;	Icon Color

	char		title_txt[18];	Title for CPX entry.
					Note: Only use 16 Characters!
					The remaining 2 positions are Nulls.
	unsigned short	t_color;	Tedinfo field for color
	char		buffer[64];	Buffer for RAM storage

	char		reserved[306];	Reserved for Expansion

	} CPXHEAD;

The first file in the link must be CPXSTART.S which jmp's to cpx_init().
In addition, it also contains the default variable storage in the DATA segment.

The user will be able to set the Resident Flag, Title Text, Title Color,
Icon Text and Icon Color with a CPX.  

The rest of the CPX file has the same format as a GEMDOS executable file.
PREFIX.PRG should be used to design and prepend the header to the CPX 
executable.  The executable part does not need to be completely relocatable,
as XControl will perform whatever relocation is necessary when it loads the
CPX.  The resource for the CPX must be built into the file and should be 
fixed up in place using the rsh_fix() facility of XControl.

	FILE:	Header	512 bytes
		GEMDOS Header	28 bytes
		Text Segment	...
		Data Segment	...
		

TERMINOLOGY:

	*.CPX:	 A standard CPX file with header ready for use.

	*.CP:	 A standard CPX file without a header.

	*_R.CPX: A resident CPX

	*_S.CPX: A Set-only CPX

	SLOT:	 A slot is where a CPX is stored in memory. There are
		 both active and non-active slots. The number of slots
		 available is decided at boot-time.  XControl will
		 create the minimum number of slots specified by the
		 DEFAULT or 1-1/2 times the number of active slots,
		 whichever is greater.  This is the ONLY time slots
		 are allocated.

	*.HDR:	 A CPX header created by PREFIX.PRG

	Event CPX A CPX that handles the event messages explicitly.

	Form CPX  A CPX that uses XForm_do to handle event messages.

	*.CPZ	An Inactive CPX
	
	
DO's:
	1)	DO remember to deallocate memory whenever appropriate.
	2)	DO use the XControl functions whenever possible.
		That's why we put them there.
	3)	DO take the time to design an appealing user interface. 
	4)	DO use graphics whenever possible instead of menu commands.
	5)	DO have  OK and Cancel buttons available for each CPX.
		Please note that it is 'Cancel' and NOT 'CANCEL'.
	6)	DO SHADOW Popup boxes. We want the user to know that
		'shadowed' boxes are Popup boxes.
	7)	Treat AC_CLOSE as 'Cancel'
	8)	Treat WM_CLOSE as 'OK'
	9)	Treat a "SAVE" action as an 'OK' by updating the 'Cancel' variables.
	

DON'T's:
	1)	DON'T have the CPX object tree exceed the
		work area ( 256x176 pixels).
	2)	DON'T have CPXs stealing interrupt vectors.
	3)	DON'T mix XForm_do() calls with Call-CPX type functions.
	4)	DON'T forget to deallocate memory whenever appropriate.
		Not doing so will fragment the system memory.
	5)	DON'T use existing ID#s for existing CPXs that were
		not written by you.
	6)	DON'T forget to close a file if your CPX opens one.
	7)	DON'T forget to open and close a VDI workstation when
		needed. DO NOT open a VDI workstation and leave it open.



WORKSTATION NOTES and BRIEFS

When a CPX wishes to perform VDI functions, the CPX must open the 
workstation, perform its duties and then close the workstation immediately.
The CPX must not leave any workstations open when it returns to accept more 
events.  The handle passed to the CPX is the Physical Handle of the Control
Panel returned by a graf_handle() call.  The proper procedure of opening a 
workstation is:

	work_in[0] = Getrez()+2; 
	for( i = 1; i < 10; work_in[i++] = 1 );
	work_in[10] = 2; 
	vhandle = xcpb->handle;
	v_opnvwk( work_in, &vhandle, work_out );


MEMORY ALLOCATION NOTES and BRIEFS

CPXs should not allocate any memory unnecessarily.  If the CPX must 
allocate memory, the CPX should perform its operation and deallocate 
the memory immediately.  An example of this is when calculating the 
amount of free memory.  The reason CPXs should not allocate memory is
because the allocated memory may be invalidated at any time by the OS.
This can occur during a resolution change or when a process exits and 
returns to the desktop.  On the TT, ALL memory is freed up during a 
resolution change, so memory fragmentation isn't a problem there.



