Menu Sharing Toolkit 3.0.3

UserLand Software, Inc.

© copyright 1992-1994, UserLand Software, Inc.

UserLand Software is located at 555 Bryant #237, Palo Alto, CA 94301. 415-326-7791, 415-326-7793 (fax). UserLand, Frontier and Frontier Runtime are trademarks of UserLand Software, Inc. Other product names may be trademarks or registered trademarks of their owners.

Email: userland.dts@applelink.apple.com. If youÕre an AppleLink user, check out the UserLand Discussion Board under the Third Parties icon. CompuServe users enter GO USERLAND at any ! prompt. On America On-Line, enter the keyword USERLAND.

Comments, questions and suggestions are welcome!

Background

WhatÕs New?

If you are already familiar with menu sharing, skip to ÒMenu Sharing Toolkit 3.0 Change NotesÓ which explains the improvements in version 3.0 of the Menu Sharing Toolkit.

FrontierÕs Editable Menu Bar

One of FrontierÕs many capabilities is that it gives the script writer an easy way to edit the contents of its own menu bar. When the user selects an item from the menu, Frontier runs the script thatÕs linked into the menu item.

Menu sharing extends that capability so that script writers can add commands to the menu bars of compatible applications. If you follow the instructions in this file, your application will support menu sharing and will work with all versions of Frontier.

Menus sharing adds a set of standard Macintosh menus at the end of your applicationÕs menu bar. Script writers can add commands to these menus using Frontier. Any changes to your menus are automatically visible as soon as the script writer switches back to your application.

In order to be menu sharing-aware, an application includes the Menu Sharing Toolkit and inserts calls to Toolkit routines in several places: on initialization; in the main event loop; before processing a menu selection and where the user presses cmd-period.

Menu sharing does not in any way alter the performance of your menu commands, it just adds new commands to your menu bar.

Applications-as-Development-Platforms

Menu sharing allows script writers to view your application as a customizable development platform. They can add commands that automate your application just as if it had an integrated scripting language. Even if your application already has an integrated scripter, it makes sense to support the menu sharing protocol because scripts can easily launch other applications and integrate their capabilities with your program and the Macintosh operating system.

Script users, usually less technical people than script writers, will see simpler commands in these menus. Examples include: prepare for a meeting, send a message to everyone working on a specific project; write a press release; or hire a new employee. There are as many potential custom scripts as there are Macintosh users. They will only be aware that these commands were Òput intoÓ your application by a friend they work with, or their organizationÕs network manager. The details of this technology are completely open to the script writer, but neatly hidden from the end-user.

By opening your product to menu sharing you offer more technical users an easy way to simplify your product for less technical users. This means your technology is more useful to a much larger number of people, and can only contribute to making your product more competitive and more profitable.

No Royalty or License Fee

There is no royalty or license fee for use of the Menu Sharing Toolkit. ItÕs provided in source form so that developers know exactly what the Toolkit is doing on their behalf, and so it can easily be adapted to different development environments.

You may include this code in any Macintosh application, but you must maintain our copyright notice at the head of each UserLand-supplied source file.

Menu Sharing is Open

WeÕre publishing source code for the client side of the menu sharing protocol, and plan to support menu sharing in all future versions of UserLand Frontier and all other software products developed by or published by UserLand Software.

We believe menu sharing should be an open specification, with freely distributed source code. Everyone wins if itÕs widely supported on both sides of the equation, by clients (the vast majority of Òsharing-awareÓ programs) and by servers (Frontier, and competitive products).

Icing on the Cake

It makes little or no sense to add menu sharing if your application is only minimally Apple Event-aware. Scripts launched from your menu bar must be able to call back into your program to open or close a window, get a value, add a record, dial the phone -- basically to do the types of things your program was designed to do.

Menu sharing, therefore, is like icing on a cake. Once youÕve added a reasonably complete set of scriptable IAC messages, the next step is to open up your menu bar to allow script writers to insert new commands that are implemented in scripts.

Who supports menu sharing?

Examples of applications that support menu sharing: Quark XPress 3.2, Think C and Symantec C++ 6.0, Aladdin Systems StuffIt 3.0 and SITcomm 1.0, Apple ComputerÕs PhotoFlash.

Thru the FinderMenu extension, shipped with Frontier, the System 7 Finder supports menu sharing.

Many more menu sharing-compatible applications are in development and will be released in the coming months.

If an application already supports Apple Events, it should also support menu sharing. ItÕs a much smaller investment in time than Apple Event support, and it delivers an important real benefit to script writers and their users.

Cookbook

Introduction

In this section weÕll step through the source code for ÒMenuSharer,Ó a very minimal Macintosh program, whose main reason for existing is this tutorial. It pays to browse through the MenuSharer source code before installing menu sharing in your program. MenuSharer is written using Think C 6.0. To view the source code, open menusharer.¹ with Think C.

The Toolkit is implemented in menusharing.c. Its header file is menusharing.h. To see how these routines are called, page through main.c. YouÕll see calls to Toolkit routines in five places:

1.  In handlemenu -- we call SharedMenuHit to run scripts selected from a shared menu.
2.  In handlekeystroke -- we call SharedScriptRunning and CancelSharedScript if the user had pressed cmd-period to cancel a running script.
3.  In handleevent -- we call CheckSharedMenus to reload the shared menus if they have been changed by a script writer.
4.  In main -- we call InitSharedMenus to set up globals and install handlers for messages that may be sent by the menu server.

Following is a step by step ÒcookbookÓ for adding menu sharing to any Macintosh application.

Step #1 -- Initialize the Toolkit

After initializing the Macintosh using InitGraf, InitFonts, etc., call the Toolkit InitSharedMenus routine:

 
if (!InitSharedMenus (&errordialog, &eventfilter))
goto error;
 

InitSharedMenus initializes menu sharing globals, and installs Apple Event handlers for the two messages it must be prepared to receive from the server.

It returns false if the initialization failed for some reason. Your application can continue, or as MenuSharer does, simply display an alert and exit to the operating system.

InitSharedMenus takes a two parameters: the address of a routine that reports an error in a modal dialog and the address of a routine that handles events. MenuSharer provides a two simple routines:

 
static pascal void errordialog (Str255 s) {
ParamText (s, "\p", "\p", "\p");
Alert (263, nil);
} /*errordialog*/
 
static pascal void eventfilter (EventRecord *ev) {
handleevent (ev);
} /*eventfilter*/
 

The error dialog routine is called when a script terminates in an error.

While a shared script is running, Frontier calls back to the event filter routine when an update, activate, OS or null event is available. This allows you to keep your windows pretty, and most important, makes it possible for the user to bring another application to the front while a script is running.


Step #2 -- Add a Call in Your Main Event Loop

In your main event loop, when you receive a null event and are not in the background, insert a call to CheckSharedMenus. You can keep track of whether or not you are in the background by setting a global flag each time you receive a suspend/resume event. ItÕs important that you only call CheckSharedMenus when youÕre active to ensure that the menubar can be updated properly.

If your menus need updating, CheckSharedMenus automatically disposes your out-of-date shared menus, and conducts a short conversation with the menu sharing server to get your updated menus.

CheckSharedMenus takes one parameter -- the resource id for the first shared menu. If CheckSharedMenus loads new menus, they are assigned resource ids starting with this number. It must be less than 255 to allow for hiearchic menus, and must be small enough so that no menu has an id of greater than 255. If there are n menus, and the parameter to CheckSharedMenus is x, then the menu ids will range from x to x + n - 1.

MenuSharer calls CheckSharedMenus in its handleevent routine in main.c.

Step #3 -- Add a Call in Your Menu Handler

In your menu handling routine, insert a call to SharedMenuHit:

 
if (SharedMenuHit (idmenu, iditem)) /*it was a shared menu item*/
goto exit;
 

SharedMenuHit checks to see if the indicated menu is a shared menu. If it returns false, the menu is not a shared menu, and you should process the command the way you normally would.

If it is a shared menu, the toolkit sends a message instructing the server to run the script. If the server is Frontier 1.0 or 2.0, this call returns before the script runs. The shared menus are disabled. They will be re-enabled when the script completes. Your program should immediately return to its main event loop.

If the server supports the Òcomponent menu sharingÓ protocol, as Frontier 3.0 does, SharedMenuHit doesnÕt return until the script has completed execution. Your program doesnÕt have to do anything special here.

MenuSharer calls SharedMenuHit in its handlemenu routine in main.c.

Step #4 -- Add code to Your Keystroke Handler

Now that weÕve given the user a way to launch a script from your applicationÕs menu bar, we have to provide a way to terminate or cancel a script. Following the standard Macintosh user interface conventions, this should be accomplished by pressing cmd-period while a script is running.

Use SharedScriptRunning to determine if a script is currently running. If it returns true, and the user has pressed cmd-period, call CancelSharedScript to terminate the script.

HereÕs the MenuSharer code, in main.c, that implements script termination:

 
static void handlekeystroke (EventRecord *ev) {
register char ch = (*ev).message & charCodeMask;
if (SharedScriptRunning ()) /*cmd-period terminates the script*/

if (((*ev).modifiers & cmdKey) && (ch == '.')) {

CancelSharedScript (); /*cancel the shared menu script*/

return;

}

handlemenu (MenuKey (ch)); /*process the normal way*/
} /*handlekeystroke*/

[Note: This is example code. If youÕre producing an international product, you may use conventions other than cmd-period for cancelling operations, and you should integrate this functionality with your Òcancel-operation-from-keyboardÓ code.]

Step #5 -- Include "menusharing.h"

Add a #include at the top of every file that calls Menu Sharing Toolkit routines:

#include <menusharing.h>

Reference Information

Introduction

The Menu Sharing Toolkit is implemented in a single C source file, menusharing.c. To use the Toolkit, include the menusharing.h header file at the beginning of every source file that calls a Toolkit routine or references a menu sharing global.

There are five menu sharing routines that your program is likely to call. Those routines are documented here. You can browse the source code to see how these routines are implemented. You may need or want to build other routines out of menusharing.c routines if you want shared menus to appear as popup menus, or if youÕre implementing shared menus in a Control Panel, XCMD or Finder extension.

In this section we try to avoid referring to Frontier by name, rather we talk about the  Òmenu sharing serverÓ or Òmenu serverÓ application. As we mentioned earlier, the menu sharing protocol is an open protocol, so itÕs possible to imagine other software playing the role of menu server.

Much of the information presented here is provided in more detail in the Cookbook Section, above. This reference section is provided primarily as a summary of the information presented in that section.

Menu Sharing Globals

All the globals needed by the Menu Sharing Toolkit are stored in a single C structure named MSglobals. Toolkit routines are provided to initialize and operate on the fields of this structure, but we provide the details and document it so you can monitor the performance of the Toolkit using a source-level debugger.

HereÕs what the declaration of MSglobals looks like:

typedef struct tyMSglobals {

OSType serverid;
OSType clientid;
hdlmenuarray hsharedmenus;
Boolean fldirtysharedmenus;
Boolean flscriptcancelled;
Boolean flscriptrunning;
Boolean flinitialized;
long idscript;
ComponentInstance menuserver;
tyMSerrordialog scripterrorcallback;
tyMSeventfilter eventfiltercallback;
} tyMSglobals;

extern tyMSglobals MSglobals;

MSglobals.serverid is the 4-character identifier of the menu sharing server. ItÕs initialized by InitSharedMenus to 'LAND' -- the 4-character creator id of UserLand Frontier.

MSglobals.clientid is the 4-character identifier of your program. ItÕs set automatically by InitSharedMenus using the Macintosh Process Manager (see IM-VI, Chapter 29). This information is passed to menu server in an IAC message to determine the set of shared menus being requested.

MSglobals.hsharedmenus is the data structure that holds the shared menus. ItÕs set to nil by InitSharedMenus. See the declaration of hdlmenuarray in menusharing.h for further information.

MSglobals.fldirtysharedmenus  is a boolean. If true, the shared menus are re-loaded the next time your application becomes the frontmost process. ItÕs set true by InitSharedMenus to force an update the first time thru the main event loop, and when the 'updm' IAC message is received from the menu sharing server when the script writer makes an editing change to your shared menu.

MSglobals.flscriptcancelled  is a boolean. If true, the currently running script has been terminated by a call to CancelSharedScript, probably in response to the user pressing cmd-period.

MSglobals.flscriptrunning  is a boolean. If true, a script is running and the shared menus are disabled. ItÕs set true by SharedMenuHit and cleared when the menu server sends a 'done' IAC message.

MSglobals.flinitialized  is a boolean. If true, InitSharedMenus worked, if false it failed. Some toolkit routines return immediately if this boolean is false.

MSglobals.idscript is the menu sharing serverÕs identifier for the currently running script. When the Toolkit sends a message to the server of the script it identifies the script by passing this value to the server.

MSglobals.menuserver is the Component Manager identifier for the connection between your application and the menu sharing server. If the toolkit is using an Apple Event-based protocol to communicate with the server, this field will be 0.

MSglobals.scripterrorcallback is the address of a routine that displays error messages. See ÒMenu Sharing Toolkit 3.0 Change Notes,Ó below, for details.

MSglobals.eventfiltercallback is the address of a routine that handles events while scripts are running. See ÒMenu Sharing Toolkit 3.0 Change Notes,Ó below, for details.

Menu Sharing Routines

Boolean InitSharedMenus (tyMSerrordialog, tyMSeventfilter);

Initializes the Menu Sharing Toolkit.
It takes two parameters: the first is the address of a routine that displays a Str255 in a dialog and waits for the user to click on OK. tyMSerrordialog is defined as follows:
typedef pascal void (*tyMSerrordialog) (Str255);
The second parameter is the address of a routine that handles incoming update, activate, OS and null events. The server calls this routine while it is running a shared script. tyMSeventfilter is defined as:
typedef pascal void (*tyMSeventfilter) (EventRecord *);
InitSharedMenus establishes initial values for the fields of MSglobals and installs two Apple Event message handlers -- one to catch the Òmenu needs updateÓ message, and another to handle the Òscript has completedÓ message.
Returns true if the program is running on a version of the Macintosh OS that supports Apple Events and if it was able to install handlers for the two Apple Events, false otherwise.

Boolean CheckSharedMenus (short);

Call CheckSharedMenus from your main event loop when you receive a null event and are not suspended via suspend/resume events. If the shared menus need updating, the Toolkit sends an Apple Event message to the server asking for your shared menus.
The MenuHandles are assigned resource ids starting with the id indicated by the single parameter. This number must be less than 255 to allow for hierarchic menus, and must be small enough so that no menu has an id of greater than 255.
Returns false if there was an error loading the shared menus.

Boolean SharedMenuHit (short, short);

Call this routine when youÕve received a menu selection. The first parameter is the id of the selected menu, the second parameter is the id of the selected menu item.
SharedMenuHit returns false if the menu isnÕt a shared menu. Your program should process the menu selection as it normally would.
If it is a shared menu, SharedMenuHit sends an Apple Event message to the menu sharing server, requesting that the script linked into that menu item be run. Shared menus are disabled while the script is running.

Boolean SharedScriptRunning (void);

Returns true if a shared script is currently running, false otherwise.
Use this routine to determine if a cmd-period should be applied to terminating the current script using CancelSharedScript.

Boolean CancelSharedScript (void);

Call CancelSharedScript when the user presses cmd-period or otherwise indicates that he or she wants the currently running script to be terminated.

Messages that the server sends

The server sends two Apple Event messages as its part of the menu sharing protocol. Both messages are handled automatically by Apple Event handlers installed by InitSharedMenus. The class for both events is the same as the 4-character creator identifier of your application.

1. When your menu has been edited and requires updating, the menu server sends an 'updm' message. The handler for this message sets an MSglobals flag true. When your program becomes the frontmost application, CheckSharedMenus sends a series of Apple Event messages to the server requesting the new menus.

2. When a shared script has completed, the server sends a 'done' message. The handler for this message simply records the fact in MSglobals and re-enables the shared menus.

Where your shared menu lives

Suppose your applicationÕs creator id is 'WXYZ'. If your application has a shared menu, itÕs stored in FrontierÕs object database at system.menubars.WXYZ.

Since MenuSharerÕs id is 'MENS', its menus are located at system.menubars.MENS.

If you implement menu sharing, you should also develop a sample shared menu for distribution with your product as part of your Frontier install file. See the ÒFrontier Install File CreatorÓ sub-folder of the Extras folder for details on creating an install file for your application.

Menu Sharing Toolkit 3.0.3 -- 7/27/94 dmb

Universal Headers, PowerPC compatibility

The Menu Sharing Toolkit can now be built using AppleÕs Universal Headers under Symantec C/C++ 7.0 or Metrowerks C/C++ 1.0 68K or PPC. Native or Òfat binaryÓ applications can be generated in the Code Warrior environment. ANSI-conformant function prototypes are used thoughout the code, so strict error checking can be enforced for Menu Sharing Toolkit projects.

MenuSharingLib

Instead of compiling the MSTK source into your native application, you can link to MenuSharingLib, a PowerPC shared library. This keeps the menusharing code separate from your applicationÕs code, and allows it to be revised independently. Note that you will need to have the library installed in your userÕs systems for your application to launch, unless you do a ÒweakÓ link and check for the ToolkitÕs presence at run time. (See you the CodeWarrior documentation for details.)

Menu Sharing Toolkit 3.0

Component menu sharing

Frontier 3.0 implements a more efficient menu sharing protocol using the System 7.1 Component Manager.

The Menu Sharing Toolkit has been updated to use the component protocol if it is available. Otherwise, it is prepared to use the Frontier 2.0-compatible protocol, or even the Frontier 1.0 protocol.

In order for component menu sharing to be the protocol it uses, the Macintosh Component Manager must be present, and the toolkit must be able to create an instance of a menu sharing server component (type 'SHMN').

When the component connection is used, scripts that call back to your program run much faster because they are running within the same process as your program. You can see how dramatic the results are by running some of the scripts in MenuSharerÕs shared menus.

Additionally, dialogs displayed by scripts now show up without a layer switch. This makes the whole effect much better for script writers. The illusion that itÕs all happening inside your program is more solid when dialogs show up without a layer switch.

Another advantage of using the Component Manager is that, with this release, it is now possible for another program or code resource to play the role of menu sharing server. Support for Frontier is only hard-coded for earlier menu sharing protocols.

Frontier 3.0 supports all earlier protocols

If you support Menu Sharing 2.0 in a shipping application, Frontier 3.0 is compatible with it.

The net effect of installing Menu Sharing 3.0 is a substantial increase in performance for script writers and users on systems that have the Macintosh Component Manager installed.

How to Upgrade

1.  Replace the old versions of menusharing.c and menusharing.h with the new versions in this package.

2.  Recompile your program. The compiler will catch two changes in the menu sharing API. The next two sections detail the changes.

Two parameters for InitSharedMenus

InitSharedMenus now takes two parameters: the address of a callback routine that displays an error dialog and one that handles events.

Check out the MenuSharer program for an example:

 
static pascal void errordialog (Str255 s) {
ParamText (s, "\p", "\p", "\p");
Alert (263, nil);
} /*errordialog*/
 
static pascal void eventfilter (EventRecord *ev) {
handleevent (ev);
} /*eventfilter*/
 
if (!InitSharedMenus (&errordialog, &eventfilter))
goto error;
 

The error dialog routine is called when a script terminates in an error.

While a shared script is running, Frontier calls back to the event filter routine when an update, activate, OS or null event is available. This allows the client to keep its windows pretty, and most important, makes it possible for the user to bring another application to the front while a script is running.

ThereÕs a new command to MenuSharerÕs ÒHairyÓ menu, called Divide By Zero. When you choose it, MenuSharerÕs errordialog routine is called. To test the event filter routine, set a breakpoint on the handleevent call and choose any command from MenuSharerÕs shared menus.

See the ÒCookbookÓ and ÒReference InformationÓ sections, above for more info.

MSglobals.scriptcompletedcallback is gone

In earlier versions of the Menu Sharing Toolkit, this undocumented callback routine played a role very similar to the error dialog described above -- it was used to report errors in scripts.

You should delete all references to MSglobals.scriptcompletedcallback in your program, if you have any.

SharedScriptCancelled calls can be deleted

We recommend that you delete all calls to SharedScriptCancelled.

These calls are not needed for versions of Frontier after version 2.0, which was shipped over a year ago, and was a free upgrade for the entire installed base.

WeÕve deleted the documentation for CancelSharedScript. It is no longer mentioned in the Cookbook section of the menu sharing documentation.

Does not require IAC Tools

Previous versions of the Menu Sharing Toolkit used the IAC Tools library to manage Apple Events. In version 3.0, we removed this dependency. All the Apple Event code needed to support menu sharing is included in the menusharing.c file.

New fields in MSglobals

There are are four new fields in the MSglobals structure: menuserver,  scripterrorcallback, eventfiltercallback, and flinitialized. Like other fields of MSglobals, these should be viewed as read-only. They are provided primarily to support debugging.

See ÒReference InformationÓ above for details on these new fields.

We deleted the scriptcompletedcallback field of MSglobals, since its function has been completely been replaced by scripterrorcallback.

Builds in MPW

menusharing.c compiles without error or warning in MPW C. Thanks to Leonard Rosenthol of Aladdin Systems for his help here.

Added #ifdefs

There are two #defines at the head of menusharing.,c that allow you to turn off support for component menu sharing, or to turn off support for Frontier 2.0 and 1.0.

We strongly recommend that you leave both #defines as they are. But if thereÕs a need to turn one or the other off, and you know what youÕre doing, now it is possible.

Menu Sharing Toolkit 2.0

No impact on the API

WeÕve made some major improvements in the efficiency of the Toolkit in version 2.0. However, these changes have no impact on the API, if you built on the 1.0 API you can simply replace the 1.0 library with the 2.0 library and rebuild your application.

Further, if you installed Menu Sharing 1.0 in a shipping application, Frontier 2.0 is compatible with it. The net effect of installing Menu Sharing 2.0 is an increase in performance for script writers and users.

Where to call CheckSharedMenus

WeÕve changed our recommendation on where to call CheckSharedMenus in your program. In SDK 1.0 we recommended calling it in your main event loop, at the same time you adjust your cursor and enable/disable menu items. In 2.0, we go more conservative, and ask that you only call CheckSharedMenus when you are not suspended and you receive a null event (WaitNextEvent returns false). See the MenuSharer program for an example.

ItÕs much faster

We use the new support for system event handlers in the IAC Tools library. If the server app has installed a set of system event handlers to implement the server side of the menu sharing protocol, we use them instead of sending Apple Events thru the operating system. The net result is virtually instantaneous loading and updating of shared menus. This performance increase is completely transparent to your program.

Compatibility

Frontier Runtime 1.0 supports the faster form of menu sharing. Frontier 1.0 does not. A version of Frontier supporting the faster protocol is in development. This Toolkit is fully compatible with present and future versions of Frontier.

UserLand will continue to support the 1.0 form of menu sharing in all future versions of Frontier and other products. If youÕve shipped a product with the 1.0 Menu Sharing Toolkit, UserLand will remain compatible with it.

Sends 'kill' message when script is cancelled

We've enhanced the protocol so that someday it may not be necessary to call SharedScriptCancelled in each of your Apple Event handlers. Instead of just setting a flag, CancelSharedScript now sends a 'kill' message to the menu sharing server (Frontier) to kill the script that was launched by calling RunSharedMenuItem, if the server has such a handler installed. If not (Frontier 1.0 does not), it depends on the SharedScriptCancelled method.

However, this protocol is not understood by Frontier 1.0, so we had to leave in the old method. The Menu Sharing Toolkit knows which version of Frontier it's talking to. If it's 1.0, it simply sets the flag and returns. If it's 2.0 or greater, it sends the 'kill' message. The net: you have to leave in your CancelSharedScript calls in your AE handlers for now. Once we have most of the 1.0 base upgraded to 2.0, it will be possible to remove those calls.

In RunSharedMenuItem

We check to see if the server is still running, if not, we remove the shared menus and return false.