Copyright (c) Hyperion Entertainment and contributors.
Application Library
Contents
Introduction
The Application Library is a multipurpose auxiliary library that provides various functions related to the development and use of applications. The very concept of application is a relatively recent addition to AmigaOS. Before, the system only distinguished between different types of program on a very low level, seeing them as either tasks or processes. This distinction might have been useful in the past when tasks (which require fewer resources in return for not being able to access DOS functions) could improve system performance. But it can hardly make a difference on today’s hardware so the trade-offs are no longer worth it. Nowadays it makes more sense to discriminate between programs that operate without the user even noticing (e.g. drivers, handlers, filesystems and other background services), and genuine full-blown applications with GUI and all.
AmigaOS alone cannot make such a distinction: it uses the Application Library as a mediator through which applications introduce themselves to the system. This process is called application registration, during which the application receives a unique identifier and is added to a public list among other applications. Once registered, the application can make use of the library’s many features:
- It can send/receive messages to/from other registered applications. The library supports a set of common control messages (commands) such as those telling an application to quit, iconify or bring its window to front. But it also allows custom messages designed for an application’s particular needs; in this respect the Application Library provides an alternative to ARexx control.
- It can turn into a “watchdog” and get notified when other applications register.
- It can use PrefsObjects, an XML-based, object-oriented system for handling program preferences. Before AmigaOS 4.x no real standard existed for storing preferences: some developers used icon tooltypes, some used proprietary formats, text or binary. The Application Library provides a format that is human-readable and easily editable in a simple text editor; that is comprehensive enough to cover even very complex settings structures; and that is fully controllable via the library, without the need to laboriously implement data parsing and verification.
- It can notify the user about, for example, completed tasks via automatic pop-up messages. These represent a practical, less obtrusive alternative to traditional requesters.
- It can easily create and manage lists of recently-used documents.
- It can register as a unique application, preventing other instances of itself from running.
- It can show its icon or display the current program state in taskbar-like applications, such as AmiDock.
- It can control the behaviour of screen-blankers. Applications that don’t want to be disturbed may prevent the blanker from kicking in, or tell other applications to “keep quiet”.
Frequently Asked Questions
Q: Should my programs register with the Application Library? | A: Yes, that would make a lot of sense. Apart from access to the handy features mentioned above, the implementation of certain library features may improve the behaviour of your application within the AmigaOS ecosystem. |
Q: After registering with the library, will my application become controlled by the OS or by other applications? | A: No. The registration process alone does not turn on any features. There is a recommendation to allow a minimum degree of control, but in the end it is the programmer who decides what functionality offered by the library will be provided and supported in his/her application. That also includes the scope of external control: if you don't want your application to be controlled beyond a certain limit, your code will simply not react to certain incoming control messages. |
Q: Doesn't the Application Library duplicate functionality already present in commodities? | A: No. The only similarity between the two is that programs register with some system library, which then acts as a control centre. Commodities provide a way to install custom input handlers into the Input Device's event stream. The Application Library's field and scope of operation is completely different (and much wider). |
Q: Can a program be registered both as an application and as a commodity? | A: Yes, that's possible – although few programs would probably benefit from that. Exceptions include application managers and taskbars (e.g. AmiDock), which may need to control other applications and, at the same time, behave like a commodity. |
Q: Being XML-based, do the PrefsObjects introduce any overhead to the operating system? | A: Hardly any. The PrefsObjects .xml preference files are, typically, only read when the application starts and written at user request, so there is minimum overhead involved. Accessing the prefs file during program runtime doesn't put any strain on the OS either, as the Application Library caches the file in a pre-parsed format in memory. |
Minimum Application Library Support
In the foreseeable future, AmigaOS will feature an application manager to control running applications. The idea is to extend or possibly even replace Exchange to offer a single, universal control point for both commodities and applications. In order to allow easy, consistent and predictable program control, developers are encouraged to register their applications and implement the following suggested minimum Application Library support:
- Provide a short description for a better identification of the application.
- Tell the OS whether the application supports iconification, that is, hiding/showing its GUI (for more information, see this section). If iconification is supported:
- make the application respond to the APPLIBMT_Hide and APPLIBMT_Unhide control messages;
- update the APPATTR_Hidden attribute accordingly each time your application iconifies or uniconifies.
- Allow the application to be shut down externally in a safe and graceful manner in reaction to the APPLIBMT_Quit message.
A failure to implement this suggested minimum will mean that the OS application manager will be unable to control your application properly and consistently. This may cause confusion on the part of the users and degrade their AmigaOS experience.
Library Opening Chores
Just like other AmigaOS libraries, the Application Library must be opened before it is used. Further, at least one of its interfaces must be obtained, depending on the functionality you require. The Application Library has two interfaces, called “application” and “prefsobjects”. You always need to obtain the “application” interface because it provides access to most library functions including application registration. You’ll only need to open the “prefsobjects” interface if you intend to make use of the PrefsObjects preferences system.
struct Library *ApplicationBase = NULL; struct ApplicationIFace *IApplication = NULL; struct PrefsObjectsIFace *IPrefsObjects = NULL; if ( (ApplicationBase = IExec->OpenLibrary("application.library", 52)) ) { IApplication = (APTR)IExec->GetInterface(ApplicationBase, "application", 1, NULL); IPrefsObjects = (APTR)IExec->GetInterface(ApplicationBase, "prefsobjects", 1, NULL); } if ( !ApplicationBase || !IApplication || !IPrefsObjects ) { /* handle library opening error */ }
Note that there is no interface called “main” like older, single-interface libraries have.
When your application has run its course, don’t forget to clean up and close both the library and its interface(s):
IExec->DropInterface((struct Interface *)IPrefsObjects); IExec->DropInterface((struct Interface *)IApplication); IExec->CloseLibrary(ApplicationBase);
Registering the Application
Application registration is a simple process during which a program informs AmigaOS that it should be treated as an application, and provides some basic information about itself: the program name, an associated URL address, or a short description. Also, certain application-related properties can be set at registration time (although some of these may be provided or changed later). Registration typically takes place at program startup; unregistration is normally done at the end of program runtime.
Application Identifiers
A successfully registered application receives a numeric identifier, which in this documentation will be referred to as appID. Other registered applications can obtain the appID from the library and use it to communicate with the respective application. AppIDs are unique numbers: the Application Library generates them incrementally on a per-registration basis. They are never used again during the same AmigaOS session, which prevents programs from incidentally addressing the wrong application after the original appID holder unregisters.
Apart from the numeric appID an application can be referred to by its name (that is, a string-type identifier). As programs can get registered in an arbitrary order, it is the name identifier that other applications must use to retrieve the correct appID. To construct a unique name, the Application Library uses a special naming scheme that combines the application name with a related URL identifier (for example, the domain name of the application’s homepage). The latter is optional but it’s recommended to provide it at registration time, to avoid possible application name conflicts. The same naming scheme is used by the library’s PrefsObjects system to create the name of the preferences file.
Registration Functions
Now let’s say we have a program, a media player called Audio Monster created by the fictitious software company SuperCoders, Inc. The piece of C code to handle its registration might look like this:
uint32 appID; appID = IApplication->RegisterApplication("AudioMonster", REGAPP_URLIdentifier, "supercoders.com", REGAPP_Description, "A media player", TAG_DONE); if (!appID) { /* report registration error and quit */ } /* do whatever your program does */ IApplication->UnregisterApplication(appID, NULL);
Note that we’ve had to alter the application name to “AudioMonster”; it is because the Application Library doesn’t allow spaces in application names. According to the naming scheme, the application will now become registered under the name “AudioMonster.supercoders.com”. (Should a previous instance of Audio Monster be already running, the library will automatically append an instance counter to the application name: the second instance will therefore be called “AudioMonster_1.supercoders.com”, the third will register as “AudioMonster_2.supercoders.com”, and so on.)
After calling UnregisterApplication() the program will be removed from the public list, and Application Library features will no longer be available.
Application Attributes
An application is described by a set of attributes. There are attributes that describe the application's properties or feature set, indicate its current state, or determine its behaviour. Some attributes are only specified at registration time and cannot be altered once they are set, while others can be changed freely after registration (as long as the application remains registered, of course). The general recommendation if to specify at registration time all properties that are not going to change during program lifetime.
Description
The REGAPP_Description tag, used in the registration example code above, tells the system what the application is all about. Although the description is an optional attribute, it is recommended to always provide it, as it will allow application manager programs to provide meaningful information to the user. Keep the description short, matter-of-fact and serious.
Uniqueness
A program can register as a unique application, thus only allowing one instance of itself to run. While the multitasking nature and tradition of AmigaOS would suggest not imposing such limits, there sometimes can be good reasons to do so. For example, the developer of the Audio Monster player might decide to make his/her program a unique application because the user would most likely gain nothing from playing several media files at the same time. Multiple program instances would only compete for screen space and system resources, possibly jeopardizing OS performance on lower-specification computers.
The following function call will register Audio Monster as a unique application:
appID = IApplication->RegisterApplication("AudioMonster", REGAPP_URLIdentifier, "supercoders.com", REGAPP_Description, "A media player", REGAPP_UniqueApplication, TRUE, TAG_DONE);
If the user now tries to launch a second instance of the program, it will fail on RegisterApplication() and the library will send a special message to the first instance informing it about the attempt. It is the developer’s responsibility to react to this message in a sensible way. Do not show error messages here: the user doesn’t need to know (or care) that an application is unique, so an error message would scold them for doing nothing wrong. The recommended behaviour is to bring the first instance to view and activate its window.
Feature Set / Control Scope
Different applications can implement different control features, and can be designed for a different scope of external control. For example, a text editor may respond to a control message (command) that tells it to create a new, blank, unnamed document, whereas in a media player such a command would be best ignored. Similarly, a simple tool with no configuration capability would want to stay clear of messages that control program settings. It is always good manners to inform the system about the scope of control your application supports, as other applications may query about (and rely on) this information when sending control messages. Always set the following boolean tags to TRUE in the RegisterApplication() call if your application implements support for the respective feature (the default value is FALSE):
Tag | Description | Implementation notes |
---|---|---|
REGAPP_HasIconifyFeature | The application supports iconification and uniconification. | Set to TRUE if (and only if) your application responds to the APPLIBMT_Hide and APPLIBMT_Unhide messages. |
REGAPP_HasPrefsWindow | The application has a dedicated Preferences (Settings) window. | Set to TRUE if (and only if) your application responds to the APPLIBMT_OpenPrefs message. |
REGAPP_CanCreateNewDocs | The application can create new documents (projects). | Set to TRUE if (and only if) your application responds to the APPLIBMT_NewBlankDoc message. |
REGAPP_CanPrintDocs | The application can print documents. | Set to TRUE if (and only if) your application responds to the APPLIBMT_PrintDoc message. |
Changing Application Attributes
Some application attributes can be set or changed after registration.
Tag | Description | Implementation notes |
---|---|---|
APPATTR_AllowsBlanker | Same as REGAPP_AllowsBlanker above. | You may want to be able to enable/disable blanking dynamically during application runtime – this what the APPATTR_AllowsBlanker tag is for. For example, a presentation program may allow blankers while the presentation is being worked on, and disable them when the presentation is started. |
APPATTR_AppNotifications | Same as REGAPP_AppNotifications above. | |
APPATTR_AppOpenedDocument | Adds a new entry to the application's Last Used Documents list. | Set this tag to indicate that your application has successfully opened a named document or project. The parameter to this tag is a pointer to the name string (i.e. a STRPTR). |
APPATTR_BlankerNotifications | Same as REGAPP_BlankerNotifications above. | |
APPATTR_CanCreateNewDocs APPATTR_CanPrintDocs APPATTR_HasIconifyFeature APPATTR_HasPrefsWindow |
Same as the four corresponding registration tags above. | Prefer setting these properties at registration time. |
APPATTR_ClearLastUsedDocs | Clears the application's list of last used documents. | Set this tag to TRUE to clear the list. |
APPATTR_FlushPrefs | ||
APPATTR_Hidden | A boolean program-state attribute indicating whether the application is currently iconified (hidden) or not. | Update this attribute each time your application GUI changes state, as application managers may query about (and rely on) this information. |
APPATTR_IconType | Changes the application icon type. | |
APPATTR_MainPrefsDict | Allows changing the application's Prefs dictionary. | |
APPATTR_NeedsGameMode | ||
APPATTR_SavePrefs | Same as REGAPP_SavePrefs above. |
Finding Applications
The Application Library maintains a public list of all registered applications. Certain special-purpose programs – application managers – will read this list (also keeping track of all subsequent registrations and unregistrations) and offer some degree of control: display information about applications and/or send commands telling them to do something. Quite naturally, most programs will not (nor are they supposed to!) act as managers but a need to talk to another application may arise. How do you find it, then?
Finding an application basically means obtaining its appID: it is an identifier as well as a “contact address” for the library's messaging system. To do this you need to know at least one of the following:
- the application name, ie. the one under which it was registered via RegisterApplication();
- the application name identifier, ie. the unique combination of the application’s name, instance number (should there be more instances running) and URL identifier – see Application identifiers above;
- the pathname pointing to the program file on disk, e.g. “Work:Utils/AudioMonster”.
Based on this information, the respective piece of code that will find our application in the system might look like this:
uint32 appID; /* if you only know the application name */ appID = IApplication->FindApplication(FINDAPP_Name, "AudioMonster", TAG_DONE); /* if you know the application name identifier */ appID = IApplication->FindApplication(FINDAPP_AppIdentifier, "AudioMonster.supercoders.com", TAG_DONE); /* if you specifically want to talk to the second running instance */ appID = IApplication->FindApplication(FINDAPP_AppIdentifier, "AudioMonster_1.supercoders.com", TAG_DONE); /* if you know the pathname to the program file */ appID = IApplication->FindApplication(FINDAPP_FileName, "Work:Utils/AudioMonster", TAG_DONE);
Once you have obtained the appID you can start communicating with the respective application.
Messaging
Messages are used extensively in AmigaOS: the inner workings of Exec or Intuition actually involve a good deal of message passing. But it’s not just the operating system that needs to communicate. Modern software applications often want to talk to other running applications. Regardless of whether this communication will, in real use, entail a simple command-driven action or an intricate exchange of data, AmigaOS provides the necessary means: inter-program communication is supported on the low level (through Exec Library’s messages and ports) as well as on the high level (using the ARexx-language scripting features). The Application Library has introduced yet another means of communication, which can be seen as lying somewhere in between the two levels.
Provided that you know its appID, you can send messages to any running application that is ready to accept them. You can even send a message to all applications at once! Basic application control can be achieved via a set of predefined messages that correspond to common program commands and functions (such as New, Open, Print, Iconify or Quit). But the library also supports custom messages, thus allowing for more sophisticated control or information exchange. The extent and complexity of messaging is fully up to the programmer: your application will only send/receive messages that you will implement (it has already been mentioned in the Frequently Asked Questions section above that registration alone does not magically turn on any features).
Furthermore, apart from this “invisible”, abstract communication taking place between application ports, you can use the library to provide real and visible information in the form of pop-up notification messages. However, as these are different and not really within the scope of the Application Library messaging framework, they are dealt with in a separate section of this document.
Note |
---|
Before we get any further with Application Library messages, it must be made clear that they should not be confused with the similarly-named AppMessages. The latter are a completely different breed, governed by the Workbench Library. They represent a specific way of communication between the Workbench desktop environment and running applications. It’s strictly one-way communication because Workbench can send AppMessages to applications but applications cannot send AppMessages to Workbench. (If you want to learn more about the use of AppMessages, consult the Workbench Library section of the AmigaOS documentation wiki). |
Data Structures
Rather than introduce yet another system for communication, the Application Library builds upon the existing Exec messaging framework. Programmers will, therefore, find working with Application Library messages rather familiar. For instance, the library uses data structures that are in fact extensions of the standard Exec message structure. Also, the usual procedure of GetMsg() and ReplyMsg() takes place when processing incoming Application Library messages (see section Message Handling below) – although the library implements its own method for sending messages.
The following three structures are defined for carrying Application Library message data:
struct ApplicationMsg { struct Message msg; uint32 senderAppID; // the appID of the sender application uint32 type; // identifies the type of message }; struct ApplicationOpenPrintDocMsg { struct ApplicationMsg almsg; STRPTR fileName; }; struct ApplicationCustomMsg { struct ApplicationMsg almsg; STRPTR customMsg; };
As you can see, structure ApplicationMsg contains a standard struct Message, the other fields are used for Application Library-specific data. The other two structures are mere extensions of the basic one: ApplicationOpenPrintDocMsg is used by certain control messages that require a pointer to a filename; the ApplicationCustomMsg structure is used for custom messages.
Upon message arrival you identify the message by reading the “type” field of the respective data structure. Similarly, if you want to send a message to an application you specify it in the “type” field.
Control Messages
The Application Library allows registered applications to send messages that control other applications. There is a set of predefined messages (or rather, commands) that can be sent to a running application, telling it – for example – that it should come to front, quit, open a document, create a new one, and so on. As these actions are common program functions, the library offers a practical and easy-to-implement way to control applications externally. The following control messages (commands) are currently provided:
Message name | Description | Implementation notes |
---|---|---|
APPLIBMT_Quit | The application is asked to shut down itself and quit. | Basically, upon receiving this message you should react as if your main program window received the WMHI_CLOSEWINDOW event. When implementing support for APPLIBMT_Quit, the programmer is required to design the program exit sequence in such a way that no unexpected data loss can occur (for example, by displaying a confirmation requester that allows the user to save work or cancel the quit command). |
APPLIBMT_ForceQuit | Same as before but this time the application shall quit immediately, without asking for saving documents etc. | Providing this command as part of the Application Library messaging framework was surely meant well but there is an inherent risk: a malevolent application could hamper the use of other applications by sending them this message and making them quit prematurely. So implement APPLIBMT_ForceQuit with care, or don't implement it at all. The official AmigaOS application manager will never try to send APPLIBMT_ForceQuit in order to shut down running applications. |
APPLIBMT_Hide | The application shall hide its interface and iconify on the Workbench screen. | An application that supports APPLIBMT_Hide and APPLIBMT_Unhide is expected to register itself with REGAPP_HasIconifyFeature set to TRUE. Basically, upon receiving this message you should react as if your main program window received the WMHI_ICONIFY / WMHI_UNICONIFY event. |
APPLIBMT_Unhide | The application shall come back from the iconified (hidden) state. | |
APPLIBMT_ToFront | The application window shall come to front. | Programmatically that entails calling the ScreenToFront(), WindowToFront() and ActivateWindow() sequence. See the end of the Message Handling section for a note on APPLIBMT_ToFront. |
APPLIBMT_OpenPrefs | The application shall open its preferences window. | Only implement if your application has a dedicated Preferences (Settings) window. An application that supports APPLIBMT_OpenPrefs is expected to register itself with the REGAPP_HasPrefsWindow set to TRUE. |
APPLIBMT_ReloadPrefs | The application shall reload its preferences. | Whatever this command is useful for. |
APPLIBMT_NewBlankDoc | The application shall open a new, blank document or project. | Applications such as web browsers can, too, make use of this command to open new program windows. An application that supports APPLIBMT_NewBlankDoc is expected to register itself with the REGAPP_CanCreateNewDocs set to TRUE. |
APPLIBMT_OpenDoc | The application shall try to open a specific document or project. | The name of the document is passed as part of the message data structure (struct ApplicationOpenPrintDocMsg: see Data Structures above). |
APPLIBMT_PrintDoc | The application shall try to print a specific document. | The name of the document is passed as part of the message data structure (struct ApplicationOpenPrintDocMsg: see Data Structures above). An application that supports APPLIBMT_PrintDoc is expected to register itself with the REGAPP_CanPrintDocs set to TRUE. |
An application will only react to messages that are implemented and supported. If you send a message to an application that is registered but does not perform any Application Library event handling, there will be no reaction at all. Further, many applications will only implement a subset of the available control commands: if a program does not have a Print function, an APPLIBMT_PrintDoc message will quite logically be ignored. There is no rule saying which or how many control messages should be implemented but you are encouraged to provide the minimum suggested support as outlined above. Implement the rest according to your application’s features and needs, and to the highest standards of security.
Custom Messages
In contrast to a control message (see above), a custom message has no meaning or purpose predefined by the library. It is a simple text string the actual meaning of which is determined by the application. There is some undeniable beauty in this concept. For instance, the application can define a set of “publicly available” commands that call a corresponding function whenever a particular command (text string) arrives from another application. This kind of external control is very easy to implement and represents a practical alternative to ARexx, while requiring less setup. Also, sender applications need not care about internals (such as knowing the ARexx port name) – all it takes is to find the receiver application and send a message to it.
The pointer to the message text string is contained in a special data structure, struct ApplicationCustomMsg.
Special Messages
There are also some special messages that an application can receive from the library or another running application:
Message name | Description | Implementation notes |
---|---|---|
APPLIBMT_Unique | If an application is registered as unique and the user attempts to start a second instance of the program, the attempt will fail and the library will send an APPLIBMT_Unique message to the first instance. | All unique applications should listen for this message and react to it: not doing so might leave the user puzzled as to why the program hasn’t started. The recommended reaction is to bring the first instance to view and focus. This may entail a couple of steps, like uniconifying (if the program is iconified at the moment), swapping screen (if the program runs on a dedicated screen), bringing the program window to front, and activating it. Basically, the APPLIBMT_Unique message should be treated in the same way as the APPLIBMT_ToFront message. |
APPLIBMT_GameModeEntered | Sent to all currently running applications if another application enters the “game mode” – that is, a state in which it doesn’t want to be disturbed. | Games or presentation programs are examples of applications that may want to run in the “game mode”. The message simply asks other applications to cooperate and “keep quiet”: not play sounds, open windows, requesters, etc. Please use the game-mode feature moderately and only send the APPLIBMT_GameModeEntered message when it is really needed. |
APPLIBMT_GameModeLeft | Informs all running application that the sender has left the “game mode”. |
Message Handling
You already know that messaging in AmigaOS takes place between message ports. Being a superset of standard Exec messages, Application Library notifications (be they predefined control messages, custom messages or special messages) are, too, sent to a message port. We’ll call this dedicated port – in order to distinguish it from other ports possibly used by the program – the notification port. The port is automatically created by the library at registration time and freed as part of the UnregisterApplication() call. Messages arriving at the port are meant to be processed within the program’s main event loop, together with other events (such as Intuition’s IDCMP messages). To process incoming messages you first need to obtain the notification port pointer from the library and then set the port’s signal bit. The signal bit is used to identify messages as Application Library notifications.
A simplified event loop code could look like the one below. Note that this example loop only waits for and processes Application Library messages. In real use you'll also want to handle other message types, such as input from the user interface (IDCMP events) or ARexx commands.
void event_loop(uint32 appID) { struct MsgPort *notificationPort = NULL; struct ApplicationMsg *appLibMsg = NULL; struct ApplicationCustomMsg *customMsg = NULL; uint32 appLibSignal = 0, sigGot = 0; BOOL done = FALSE; /* Obtain pointer to Application Library's notification port and set the signal bit. */ IApplication->GetApplicationAttrs(appID, APPATTR_Port, ¬ificationPort, TAG_DONE); appLibSignal = (1L << notificationPort->mp_SigBit); /* Go into the event loop. */ while (!done) { /* Wait for a signal to arrive. */ sigGot = IExec->Wait(appLibSignal); /* Process all Application Library messages. */ if ( sigGot & appLibSignal ) { /* Obtain pointer to the message. */ while ( (appLibMsg = (struct ApplicationMsg *) IExec->GetMsg(notificationPort)) ) { /* Identify the type of message. */ switch (appLibMsg->type) { case APPLIBMT_Quit: case APPLIBMT_ForceQuit: done = TRUE; break; case APPLIBMT_ToFront: /* Make the program window come to front. */ break; case APPLIBMT_Hide: /* Iconify the program. */ break; case APPLIBMT_Unhide: /* Uniconify the program. */ break; /* Process the custom message as you like. Here we just use printf() to output the message text. */ case APPLIBMT_CustomMsg: customMsg = (struct ApplicationCustomMsg *) appLibMsg; printf("The message text is: %s\n", customMsg-> customMsg); break; /* Process any other Application Library messages. */ } /* Return the processed message to the sender so that it can be freed. */ IExec->ReplyMsg( (struct Message *) appLibMsg); } } }
Like all Exec messages obtained via the GetMsg() function, Application Library messages must be replied to, i.e. returned to the sender after they have been processed. This is what the last command does in the code above. Remember that all message resources are freed after ReplyMsg() so should you need to use the message data (for example, the custom message text string) beyond the event loop, you must copy it to a memory storage of your own.
Also remember that the notifications are addressed to an abstract application – this makes them rather different from IDCMP messages, which are always sent to a particular window (Intuition is unaware of the application concept). Whereas Intuition stops sending IDCMP messages as soon as the window becomes closed/iconified, the Application Library keeps passing messages as long as the application is registered, regardless of program window state. So be smart in your code when processing Application Library notifications: check for the availability of the program window and perform uniconification when necessary. For example, if an APPLIBMT_ToFront command is sent to your iconified application, there is no window to come to front; you need to uniconify it first and only then call the ScreenToFront(), WindowToFront() and ActivateWindow() sequence. The mistake of referencing a non-existing window is as silly as it is easy to make!
Sending Messages
While incoming notifications are processed pretty much like normal Exec messages, the Application Library implements its own method for message sending. It takes three simple steps to send a message to another application:
- Prepare the respective data structure: specify the type of message and supply the sender's identifier (appID).
- Find the receiver application.
- Send a message to it via the SendApplicationMsg() function.
For example, if you want to tell the Audio Monster application to quit, you send it an APPLIBMT_Quit message like this:
struct ApplicationMsg appMsg; // message data structure uint32 audioMonsterID; // identifier of the receiver /* Step 1: Prepare the message data structure. */ appMsg.senderAppID = appID; // identifier of the sender appMsg.type = APPLIBMT_Quit; // type of message /* Step 2: Find the receiver application. */ if ( (audioMonsterID = IApplication->FindApplication(FINDAPP_Name, "AudioMonster", TAG_DONE)) ) { /* Step 3: Send the message. */ IApplication->SendApplicationMsg(appID, audioMonsterID, &appMsg, APPLIBMT_Quit); }
Should you want to send your message to all currently registered applications at once, just use a receiver appID of 0.
Sending custom messages is done in a similar fashion, the only difference is that you must use a dedicated data structure instead of the generic struct ApplicationMsg. As our Audio Monster application is a media player, it may as well have defined a set of commands for external control, one of them being “Start playback”. Now if another application wants to tell Audio Monster to start playing, it will need to send the custom message like this:
struct ApplicationCustomMsg customMsg; // custom message data structure uint32 audioMonsterID; // identifier of the receiver /* Prepare the message data structure. */ customMsg.almsg.senderAppID = appID; // identifier of the sender customMsg.almsg.type = APPLIBMT_CustomMsg; // type of message customMsg.customMsg = "Start playback"; // message text /* Find the receiver application. */ if ( (audioMonsterID = IApplication->FindApplication(FINDAPP_Name, "AudioMonster", TAG_DONE)) ) { /* Send the message. */ IApplication->SendApplicationMsg(appID, audioMonsterID, (struct ApplicationMsg *) &customMsg, APPLIBMT_CustomMsg); }
Pop-up Notifications (Ringhio Messages)
As from the introduction of the Ringhio server in AmigaOS 4.1 Update 1, registered applications can inform the user via notifications displayed in a small pop-up box. These are sometimes called Ringhio messages because the server provides means through which the messages are communicated visually (in other words, Ringhio handles the actual display of messages sent by the Application Library).
The pop-ups function similarly to requesters in that they show a text message; but unlike requesters, Ringhio messages do not require user interaction or acknowledgement. They just show up briefly and disappear – which makes them great for informing about less significant, matter-of-fact events such as that a certain task has been completed (this is especially helpful if the application is hidden or runs on a different screen, as the user is kept informed about something that is currently beyond his/her visual control).
Of all the types of Application Library messages, pop-up notifications are surely the easiest to program. They don’t require any setup or event handling; all it takes is a single function call:
uint32 result; result = IApplication->Notify(appID, APPNOTIFY_Title, "Important Message", APPNOTIFY_Text, "Your socks need changing!", APPNOTIFY_PubScreenName, "FRONT", TAG_DONE);
The first parameter is your application’s appID received from the registration function. The APPNOTIFY_Title tag specifies a short heading for the pop-up box while APPNOTIFY_Text contains the actual message text. Certain limits to text length apply to ensure that the pop-up remains easy to read: 64 characters for the heading (title) and 128 characters for the text (160 characters as of Ringhio 53.23). This particular message will display on the frontmost public screen (as specified in the APPNOTIFY_PubScreenName tag), which may as well be the right setting for most applications. You can of course provide any other public screen name – or you can call Notify() without this tag and let the library use the default, which is the Workbench screen.
The result value shows whether the call was successful; 0 means that an error has occurred and Ringhio failed to display the pop-up. Depending on the significance of the message, you may want to react upon the failure and inform the user through other means of communication, such as a requester.
The look and position of the pop-up box is configurable from the Notifications editor located in the Prefs drawer on your system partition. This is Ringhio’s preferences editor: as we have explained above, it is Ringhio that is responsible for the actual display of notification messages. The pop-up box can also show a small custom image (like the one in the picture above) to make the message more easily identifiable as related to a particular application. The maximum size of the image is 32x32 pixels. It can be any format, provided that there is a datatype for it installed in the system. Being application-specific, these images logically cannot be configured system-wide through the Notifications editor; instead, they are specified as part of the Notify() call. Note that a full path to the image file must be provided:
IApplication->Notify(appID, APPNOTIFY_Title, "Important Message", APPNOTIFY_Text, "Your socks need changing!", APPNOTIFY_PubScreenName, "FRONT", APPNOTIFY_ImageFile, "PROGDIR:Images/AudioMonster.jpg", APPNOTIFY_BackMsg, "All right, ma!", TAG_DONE);
But what is this APPNOTIFY_BackMsg thing? It has been mentioned above that notification messages do not require user interaction: they display some text and go away. Nevertheless, Ringhio can send the application a custom message (called “back message” – hence the name of the tag) if the user has double-clicked on the message box. As the code snippet shows, this custom message takes the form of a text string (within the Application Library messaging context, custom messages are always text strings). It is sent to the same event stream, and is supposed to be processed within the same event loop, as other Application Library messages – see the Message Handling section for how to go about it.
Whether receiving a “back message” would be useful for a particular application, and whether it would make sense to react upon it, is decided by the programmer. The reaction (if any – often none is needed) should be sensible and logical. Make sure not to misuse the feature! Note that Ringhio messages are not requesters, and double-clicking on the message box does not really equal pressing a requester button. Therefore, receiving the message could be interpreted as the user’s acknowledgement of what Ringhio has said, but never as a selection of an option. Do not use Ringhio to request input via the back-message feature: the text of the message should be a statement, not a question or an offer of choice. Considering this logic, the double click means “I understand”; it doesn't mean “Yes” or “No”.
If the text string provided in the APPNOTIFY_BackMsg tag is an URL, the feature works rather differently. Instead of sending the message to the event stream, this particular URL is opened in your default web browser. Because the process is done through the Launch Handler, your system should be recent enough to have it (the Launch Handler was introduced in AmigaOS 4.1 Update 1). The following piece of code will open Audio Monster’s homepage if the user double-clicks on the Ringhio box. The last in the tag list, APPNOTIFY_CloseOnDC, causes the message box to disappear right after the double click.
IApplication->Notify(appID, APPNOTIFY_Title, "Important Message", APPNOTIFY_Text, "Your socks need changing!", APPNOTIFY_PubScreenName, "FRONT", APPNOTIFY_ImageFile, "PROGDIR:Images/AudioMonster.jpg", APPNOTIFY_BackMsg, "URL:http://www.supercoders.com", APPNOTIFY_CloseOnDC, TRUE, TAG_DONE);
Pop-up Message Style Guide
- Keep the message text short. Give the user chance to read the entire message before it disappears.
- Do not use pop-up messages to report errors. An error is usually a serious situation, and as such it cannot be dismissed by simply displaying an automatic message (which can easily get missed or overlooked).
- Use the pop-up message facility with moderation. Displaying the messages too frequently might hamper the workflow, and would most probably annoy the user.
PrefsObjects
Introduction
Many applications are user-configurable so they need a way to store their settings; we traditionally use the term preferences or simply prefs in the AmigaOS context. It may come as a surprise that before version 4.x, AmigaOS had no real standard for storing preference data. Developers used various, often proprietary solutions: icon tooltypes, IFF-based formats, text files mimicking the Windows .ini format, or binary formats. Some of them are still very popular (tooltypes) or even used by the OS itself (some system preferences are stored as IFF-PREF files). While this is not a place to go into detail and discuss their particular advantages and disadvantages, let’s mention two common drawbacks that ultimately led to the development of the Application Library PrefsObjects system:
- All of the solutions above require you to implement your own preferences handling code (that is, routines for parsing, verification, and saving).
- None of the various formats used have ever provided a smart enough way to administer complex, structured settings.
Since its introduction in AmigaOS, PrefsObjects has largely been promoted as being XML-based and therefore human-readable and easy to edit. Yet using an industry standard format that is platform-independent and supports Unicode is not the biggest “selling point” of PrefsObjects. What makes it different from (and superior to) previous solutions is not the fact that it is XML-based but, rather, that it is object-oriented. Among other things, this property also helps address the two drawbacks mentioned above.
Imagine, for example, a program that uses a plug-in system. Both the main program and its plug-in modules are configurable. Instead of having ten or more individual preference files, you’ll likely want a single file containing settings for the program as well as for the plug-ins. This naturally introduces a certain hierarchy. Plus, as some plug-ins may be delivered by third parties, the particular structure of the prefs file is somewhat beyond the control of the main program: the amount, contents and sequencing of data in the file depends on which plug-ins are installed and on how (and when, if ever) they store their settings. You would have to take all of this into account when implementing your own prefs-handling routines, which means a good deal of work.
Object Types
PrefsObjects tackles the task by seeing data as objects, and by providing common functions (methods) for object manipulation. Loading, adding, updating or removing preference objects (be they single items or entire clusters of settings) then becomes a matter of calling the respective function – “invoking the method on the object”, as we say in object-oriented programming. If you are acquainted with this philosophy (as used, for example, in Intuition’s BOOPSI framework), you will find working with PrefsObjects very easy and straightforward.
PrefsObjects distinguishes between the following object types:
Dictionary | A container to encapsulate (embed) other objects. The prefs file usually has a Dictionary object at the top level. |
Array | A container to encapsulate other objects. The difference between a Dictionary and an Array is in object referencing: in Arrays encapsulated objects are referenced by an index, while in a Dictionary they are referenced by a name (key string). |
Number | An object to carry a long integer, double, or a boolean value. |
String | An object to carry a text string. |
Date | An object to carry date and time information. |
Binary | An object to carry arbitrary binary data. However, binary data should be avoided as much as possible because it's restrictive in its form and is not future-proof, as it does not enable addition or removal of data without compromising compatibility. |
The Interface and its Functions
It has been mentioned above in the Library Opening Chores section that the Application Library has two interfaces, called “application” and “prefsobjects”, respectively. If you want to implement your program preferences using the PrefsObjects system, you must obtain the “prefsobjects” interface: all the necessary functions (methods) are contained therein. Naturally, your application must be registered before you can make use of PrefsObjects.
All operations related to settings – retrieving, adding and changing –, as well as all operations related to the prefs file itself – creation, loading and saving – are performed solely through the library functions. Some operations can even be automated, sparing the programmer from unnecessary hassle.
The Preferences File
Prefs files are standard XML documents. They are encoded in UTF-8 (which AmigaOS doesn't support directly) but that doesn't necessarily pose a problem. First, you rarely need to edit prefs files manually – nor are you encouraged to do so, unless the file has become corrupted or contains a setting that prevents the application from running properly. Second, characters beyond the ASCII range are rarely used in preferences, so the prefs files will likely be readable and editable even in a text editor that has no notion of UTF-8. And third, you can always use the dedicated PrefsObjectsEditor, located in the Utilities drawer on your system disk, which allows easy and safe editing of PrefsObjects files (usage of this tool is, however, out of the scope of this page).
File Structure
It's recommended – but not mandatory – that a preferences file starts with a Dictionary object. The advantage of using a Dictionary is that objects stored in it are identified by a name (called a “key”). Using named objects is very practical. Unlike in Arrays, where objects are indexed, the order of data is quite irrelevant as long as you deal with objects on the same level. Your application can retrieve, add, modify, re-sequence or remove individual objects in an arbitrary order without having to worry about breaking anything: a named object will always be addressed properly if it exists.
Object names (keys) must be unique on each particular level. In other words, you cannot have two settings items (objects) with the same key in the same dictionary. However, identical object names can be used in different dictionaries. The following example shows a prefs file structure that embeds two dictionaries named “Screen settings” and “Window settings”. As you can see, identical keys (“Name”, “Width” and “Height”) are used without collision:
– "Root" (Dictionary object) – "Screen settings" (Dictionary object) – "Name" (String object) – "Width" (Number object) – "Height" (Number object) – "Window settings" (Dictionary object) – "Name" (String object) – "Width" (Number object) – "Height" (Number object)
The example also illustrates the hierarchical nature of the PrefsObjects system: objects can be embedded inside other objects.
Reading the File
In most cases, there will be just one preferences file associated with your application. If all you need is simply read settings from the file (and save them upon user request), it is recommended that you let the Application Library handle the file access. Registering your application like this
appID = IApplication->RegisterApplication("AudioMonster", REGAPP_URLIdentifier, "supercoders.com", REGAPP_Description, "A media player", REGAPP_LoadPrefs, TRUE, TAG_DONE);
will tell the library to access the prefs file automatically, under its default name and from the default location. The default naming scheme for prefs files uses the application name combined with the URL identifier. Also, the .xml extension is appended at the end of the name to identify the document format. So with this particular registration call, the default file name will be “AudioMonster.supercoders.com.xml” and the library will try to access it from the ENV: directory. (Don't worry, both the file name and location can be changed.) If the file is found, the library will internally allocate a PrefsObject and read the settings into it. If the file is not found, the library will allocate an empty PrefsObject for you. In both cases you can subsequently obtain the PrefsObject pointer just like you would ask for any other application atribute:
PrefsObject *mySettings = NULL; IApplication->GetApplicationAttrs(appID, APPATTR_MainPrefsDict, &mySettings, TAG_DONE); if ( mySettings ) { /* you can now retrieve the individual settings from the object, or store them in it */ }
Populating the File
Each of the Object type has a corresponding dispatcher to handle operations on this kind of object. The following table indicates the mapping:
Type | PrefsObjects dispatcher name |
---|---|
dictionary | PrefsDictionary |
array | PrefsArray |
number | PrefsNumber |
string | PrefsString |
date | PrefsDate |
binary | PrefsBinary |
Allocating Objects
All those dispatchers share a common set of methods among them are allocation and releasing of the object.
To allocate a new dictionary all that is needed is to call the ALPO_Alloc method on the PrefsDictionary dispatcher like this:
PrefsObjects * pDict = IPrefsObjects->PrefsDictionary(NULL, &nError, ALPO_Alloc, 0, TAG_DONE);
Should a string be needed the call would be similar using PrefsString instead:
PrefsObjects * pStr = IPrefsObjects->PrefsString(NULL, &nError, ALPO_Alloc, 0, TAG_DONE);
Releasing Objects
When the object is not needed anymore it can be released calling the ALPO_Release method of the dispatcher and providing the object to release like this:
IPrefsObjects->PrefsDictionary(pDict, &nError, ALPO_Release, 0, TAG_DONE); IPrefsObjects->PrefsString(pStr, &nError, ALPO_Release, 0, TAG_DONE);
Reference Counting
Note that because objects can be referenced several times, PrefsObjects maintain a reference count of any created object. Deallocation will really take effect when this count reach 0, else releasing an object only decreases the count by 1.
In some special cases it may be interesting to have more direct control over this automatic mechanism. This is exactly the purpose of methods ALPO_GetRetainCount and ALPO_Retain.
The first one allows retreival of the current count, unlike previous methods this one requires an argument to instruct where to store the retrieved count. This argument takes the form of a pointer to an int32:
int32 nCount = -1; IPrefsObjects->PrefsDictionary(pDict, &nError, ALPO_GetRetainCount, &nCount, TAG_DONE);
The second one allows to explicetly increase the count by 1. This can be needed for several reasons the main one being transfer of ownership.
Automation
By default, the prefs file is stored in and loaded from the ENV: and ENVARC: system directories. The default location can nevertheless be overridden at registration time. For example, your application may want to use its own subdirectory in the ENV(ARC): path. This is done by passing the REGAPP_ENVDir tag-value pair in the RegisterApplication() call. The following line of code will cause saving the prefs file into “ENV:AudioMonster/” and “ENVARC:AudioMonster/”.
REGAPP_ENVDir, "AudioMonster",
The default naming scheme for the prefs file uses the application name combined with the URL identifier. Also, the .xml extension is appended at the end of the file name to identify the document format. So if our application registered under the name of “AudioMonster” and with the URL identifier “supercoders.com”, the default preferences file name will be “AudioMonster.supercoders.com.xml”. This yields a unique file name but might look a little quirky so the library provides a way to override the default naming scheme. This is done by passing the REGAPP_ CustomPrefsBaseName tag-value pair in the RegisterApplication() call. The following line of code will cause saving the prefs file as "AudioMonster.xml":
REGAPP_CustomPrefsBaseName, "AudioMonster",