The existence of an AppPort is also a pre-requisite for program window iconification. Even if you specify WINDOW_IconifyGadget, TRUE in your window object definition (and the resulting window displays a nice Iconify gadget), iconification will not work unless you specify the AppPort as well. This is quite logical: when a program is iconified, Workbench needs to tell it when there's time to "wake up" and reopen the window - without an AppPort, Workbench would have nowhere to send the message.
A message port is a type of system resource so the AppPort for your window must be allocated and initialized through the AllocSysObject() function (and freed accordingly; see section 7):
- struct MsgPort *winAppPort;
- winAppPort = (struct MsgPort *)IExec->AllocSysObject(ASOT_PORT, NULL);
To make it all work, your window object definition should contain the following tags:
- WINDOW_AppPort, winAppPort, /* the application port */
- WINDOW_AppWindow, TRUE, /* we want an AppWindow */
- WINDOW_IconifyGadget, TRUE, /* allow iconification */
You do not need to supply the WINDOW_AppWindow tag if you only require iconification – providing the AppPort is enough.
5. AppMessage handling
The key to ReAction’s message handling mechanism is the WM_HANDLEINPUT method, which processes all input events: gadget selections, menu picks, key presses, and the like. The result of the method call informs you of most events that the window has received. I say most - but not all. The pre-defined result values (see classes/window.h) only cover the most common types of input events: should you need others, you must use an extension called a hook. A typical example would be an ”unsupported” IDCMP event, such as GADGETDOWN. Window Class does support these, it just considers them less common and less likely to happen than other types of event (such as GADGETUP or MENUPICK). So it leaves it to the programmer to install an IDCMP hook and process all other events there.
AppMessages are handled in a similar fashion. The only AppMessage that the WM_HANDLEINPUT method can identify by the result value is WMHI_UNICONIFY, that is, Workbench telling the application to reopen its window. For other AppMessages you must provide an AppMessage hook and pass the hook pointer to the window object via the WINDOW_AppMsgHook tag.
You will notice that in older documentation, namely the ROM Kernel Manual: Libraries, AppMessages are handled differently in the input loop. The technique required setting up a signal for Exec’s Wait() and then processing the message via GetMsg() / ReplyMsg(). This is not the way to do it in ReAction. Using the old technique in combination with the WM_HANDLEINPUT method would mean mixing things up. Use the AppMessage hook instead.
6. The AppMessage hook
In case you have never used a hook before and are not sure what we are talking about: hooking is a practice of intercepting function calls, messages or events with custom code; this code is called a hook function. Whenever specific conditions are met, the currently running section of the program code gives control to the hook function and the custom code is executed. To bridge the intercepted code section and the hook function, you need a data structure called a hook. Once in place, the hook is responsible for calling the hook function and providing it with data from the intercepted code.
The AmigaOS API employs hooks quite extensively: many system components allow hooking in order to extend their functionality in a transparent and standardized fashion. ReAction’s WM_HANDLEINPUT method works this way, too - instead of checking for every possible type of event, it handles a limited set and lets you install a hook if you need more.
In order to use a hook, you have to:
- open Utility Library (which provides the hooking functionality);
- write the code for the hook function (see 6.1);
- allocate and initialize memory for the hook data structure (see 6.2);
- install the hook by passing the data structure pointer to the appropriate component that is to be intercepted (see 6.3).
6.1 The hook function
The hook function is a custom function you write in order to be able to handle a specific situation that arises from intercepting the main code (such as, processing a message). The function must be designed to accept three arguments, whose order and type is given. On 68K systems, the arguments had to be put in specific CPU registers (A0, A2 and A1) – this is no longer the case under AmigaOS4. Using hooks is easier and more transparent now.
The first argument is a pointer to struct Hook, the hook data structure (see 6.2 below). The second is a generic pointer to an object and is dependent on the context the hook function was called from. For example, if the hook call was triggered by a ReAction gadget or window, the argument will point to the respective object instance. (AppMessage hooks are called by the Window Class so the object will point to the window object.) The third argument is a pointer to a message, the type of which is also context-dependent. For instance, when providing an IDCMP hook, the third argument will point to a struct IntuiMessage; if we are providing an AppMessage hook, the argument will point to a struct AppMessage.
All it takes to process AppMessages inside the hook function is to read from the message structure, identify the type of message, and act accordingly. In the following code snippet we’ll process an AppWindow message, supposedly a notification that a file icon has been dropped into our window. We’ll obtain the name of the file and call our hypothetical loading routine:
- #include <workbench/workbench.h>
- #include <workbench/startup.h>
- #define FNAME_MAX 2048 /* maximum filename length */
- void my_hook_function(struct Hook *hook, Object *object, struct AppMessage *msg)
- struct WBArg *argPtr = NULL;
- char fileName[FNAME_MAX];
- if ( msg->am_Type == AMTYPE_APPWINDOW )
- /* An icon was dropped into the AppWindow.
- Check the AppMessage class and the number of arguments
- to make sure that we really have a file to open. */
- if ( (msg->am_Class == AMCLASSICON_Open) && (msg->am_NumArgs > 0) )
- /* Obtain the filename and load the file. */
- argPtr = msg->am_ArgList;
- IDOS->NameFromLock(argPtr->wa_Lock, fileName, FNAME_MAX);
- IDOS->AddPart(fileName, argPtr->wa_Name, FNAME_MAX);
6.2 The hook structure
Now that our hook function is ready, we must prepare the hook data structure. Under OS4, this structure is considered a system resource object so you are supposed to allocate it using AllocSysObjectTags(). Do not initialize its members directly, as older documentation shows. Instead, declare a pointer to struct Hook and initialize it by passing the tags:
- struct Hook *appMessageHook;
- appMessageHook = IExec->AllocSysObjectTags(ASOT_HOOK,
- ASOHOOK_Entry, my_hook_function,
- ASOHOOK_Subentry, NULL,
- ASOHOOK_Data, NULL,
ASOHOOK_Entry points directly to the hook function. The function can receive arbitrary data through the ASOHOOK_Data tag (we do not need any for our AppWindow hook so the pointer value is NULL here). This data can then be accessed inside the hook function by using the hook pointer, which the function receives as its first argument. For example, if we pass the string “Hello world!” as hook data (ASOHOOK_Data, “Hello world!”, ... ), you can print out the string inside the hook function like this:
- void my_hook_function(struct Hook *hook, Object *object, struct AppMessage *msg)
- puts( (STRPTR) hook->h_Data);
6.3 Installing the hook
All that is left to do is install the AppWindow hook in the ReAction window object. You can do this at object creation time, by putting “WINDOW_AppMsgHook, appMessageHook, ...” in the window definition tag list, but you can also do it later, using SetAttrs():
- IIntuition->SetAttrs(windowObject, WINDOW_AppMsgHook, appMessageHook, TAG_END);
7. Resource disposal
Apart from the ReAction window we have defined and used as our AppWindow, we have two other resources to dispose of when the program has run its course: the application port and the hook. These are not BOOPSI objects so the Window Class does not free them automatically at DisposeObject() time. After you dispose of the window object, you must call FreeSysObject() to release the memory allocated for the AppPort and the hook, respectively:
- IExec->FreeSysObject(ASOT_PORT, winAppPort);
- IExec->FreeSysObject(ASOT_HOOK, appMessageHook);
8. Example code
To give you a better picture of how AppMessages are handled by Window Class and processed in the code, I’ve made a full working example in C (see attached). The program opens a ReAction window with a display area that is, in fact, a big read-only Button Gadget. The window is set up as an AppWindow of course. If you drop a file icon into the window, the gadget will change its text and display the file name.