Pegasus Mail System, Copyright (c) 1990-95, David Harris, All Rights Reserved. Extensions Support in Pegasus Mail for Windows 1: Introduction In Pegasus Mail for DOS v3.0, I introduced a resource-based forms system which allowed people to create new ways of sending pre-formatted mail using Pegasus Mail's internal services. While the DOS version's Forms Manager added a useful and quite powerful level of functionality to the program, it suffered from some drawbacks - it required users to learn a new and quite complex language, was somewhat difficult to use, and provided (on the whole) insufficient means of hooking into the internals of Pegasus Mail. When I reached the stage of feeling ready to incorporate forms support into the Windows version of Pegasus Mail, I was faced with a quandary - how to provide something which offered an adequate compromise between infinite flexibility and maximum ease of use. I sat and thought for a long time about exactly what uses forms would have in an e-mail system and eventually reached the conclusion that you get what you pay for... As the old saying goes, "Provide a system easy enough for any idiot to use and only idiots will use it"... In order to provide a forms system which is capable of handling any imaginable task which might be asked of it, the price is necessarily a fairly high level of complexity and richness. Balancing this implementational cost came a realization that forms could be either a gimmick, or a mission-critical component in a system: clearly, in a situation where adding a form is a mission- critical requirement, resources and effort become justifiable; as for that class of user who simply wants a new toy, well, I'm afraid they will be disappointed. Once I began writing the forms interface, it became apparent almost immediately that it would be possible to do far more than simple forms programming with it, so accordingly, I renamed it an "Extensions" interface. The process of developing an extension for Pegasus Mail for Windows involves direct, Windows-level programming and no small amount of familiarity with the intricacies of writing for the Windows environment: on the positive side, because an extension consists of executable code which takes advantage of the internal services Pegasus Mail offers, there is practically no limit at all to what one can do. Because many of the mechanical tasks associated with mail processing are performed on a server/client basis for the extension by WinPMail, the extension in its most basic shape is simply a glue interface which can be potentially quite easily produced simply by examining and modifying the source code for any of the sample extensions provided with this kit. Furthermore, WinPMail deals with the vast bulk of the complex issues associated with window management and user interaction, so an extension can concentrate in a more focussed way on the actual task it has to perform. 2: Requirements In order to write a WinPMail extension, you will need access to the following tools or resources: * Borland C++ version 3.1 or later (recommended) or any other development environment capable of producing a standard Windows DLL module. * A good understanding of Windows messages and programming. If you don't have this already, you're probably in trouble, but if you're masochistic enough to want to continue anyway, then Charles Petzold's excellent introduction to Windows programming will probably be a mandatory place for you to start. * Considerable patience; the process of developing an extension is much easier than writing your own mail application, but you can still expect to spend some time debugging, cursing, swearing and all the other usual fun pastimes associated with normal Windows programming. 3: Overview To WinPMail, an extension is simply a special piece of user-supplied code which it agrees to host in one of its MDI (Multiple Document Interface) windows. WinPMail offers an extensive "API" of over 70 functions to extensions and co-operates with them to ensure correct presentation of the extension's window, but otherwise places very few restrictions on what it may do. extensions come in two flavours - "Reader" extensions, and "Composer" extensions. "Reader extensions" are special extensions which Pegasus Mail can ask to display or handle mail messages which match particular criteria, instead of using its own built-in message reader routines. Reader extensions are opened automatically by WinPMail when the user asks to read a message matching certain criteria (supplied by the extension developer), but can never actually be directly triggered by the user - they are always triggered by WinPMail in response to a condition becoming true. Once a Reader extension has been triggered, it can take any action it wishes and has access to the full API, including a number of specific functions allowing it to access the data from the triggering message. Reader extensions are intended to provide custom presentation windows for particular types of message (usually messages generated using a matching Composer extension) and can be used to provide extended automatic processing of messages (much like WinPMail's own mail filtering rules). Unlike Reader extensions, Composer extensions can ONLY be activated by the user - they are never invoked automatically (other than the possible option of being activated at startup). Composer extensions are primarily intended to provide alternative ways of entering message data then formatting it for transmission, and hence have considerable control over the mail messages they generate. WinPMail provides full management for Composer extensions, including a floating selection window from which the user can load new extensions at any time. It is important to note that although the extensions support is primarily intended to offer mail-oriented extensions to Pegasus Mail, extensions can in fact do almost anything they want - a good example is either of the directory service clients supplied with Pegasus Mail (PH and Finger) - they interact with the user and perform reasonably complex user lookups using the TCP/IP protocol, but never so much as send or examine a single mail message. Extensions could conceivably be used for almost any purpose, including mail gateways, system monitoring extensions, graphics editors, spreadsheet modules, OLE or OpenDoc integration, DDE support - just about anything you could write code for. There is a basic assumption that if an extension needs to interact with the user in any way, then it will create a standard Windows dialog to do so. WinPMail's Extensions Manager provides a mechanism which allows a standard dialog to be fitted into a regular WinPMail MDI client window, and if this paradigm is followed, your extension code will be nothing more than a simple Windows modeless dialog handler - WinPMail will occasionally send specific messages to an extension advising of certain events, but you need not necessarily respond to any of these messages. Extensions do not have to provide a dialog - for instance, an extension which simply logs events to a file might need no user interface at all; in cases like this, the extension can export a single function to which WinPMail will send notifications, and the enclosing MDI window will be hidden. It is important to note, however, that every instance of an extension will always have a WinPMail MDI "parent" window associated with it, whether or not it is visible or interacts with the user. Extensions gain access to WinPMail internal services by sending standard Windows- style messages to this MDI window. 4: Writing an extension To WinPMail, an extension consists of two parts - a "Form Fact File" (described below) and a DLL which it should load and with which it should interact. To write an extension, you need to use any language tool which can create a standard Windows DLL; while Pegasus Mail is written using Borland C++, there is no requirement that you use this language to create an extension, provided the language you use supports the standard Windows interface. The examples in this section presume that you are using Borland C. A good starting point for writing an extension is to use the source code from one of the sample extensions provided with Pegasus Mail as a model. Using the Borland IDE, create a new project (or load a model) and set the code generation and output file format to "Windows DLL". You can use whatever memory model is most appropriate for your purposes, but be aware that many of the Windows messages you have to send to the Extension Manager presume that you are using pointers to FAR data. I personally find that the Large memory model is the easiest and least complicated to use for projects like this. Next, create or open the main file for the extension. This will be a standard Windows program file; you should include as usual, along with whatever other header files you need. You must also include the file "wpmforms.h" supplied with this kit. Wpmforms.h contains all the definitions necessary to interface with the Extensions Manager, and is heavily commented; you should refer to this file for information and syntax on all the API calls available to extensions. Also in the main file you should create the standard Windows LibMain() function, which almost always consists of the following code: int FAR PASCAL LibMain (HANDLE hInstance, WORD wDataSeg, WORD wHeapSize, LPSTR lpszCmdLine) { if (wHeapSize > 0) UnlockData (0); return 1; } If your extension is going to use a dialog, you should also register the class for your dialog window here. Note that dialog windows in extensions are by definition modeless, so they must have a registered class with a standard window handler. Examine the PH.C sample file for an example of how to register a window class in LibMain. The next step is to create the initialization function WinPMail calls when it loads the extension. This function >>must<< be called FORMINIT, and should have the following declaration: WORD FAR PASCAL _export FORMINIT (WORD version, int variant, HWND hParent, char *data, HWND *hDialog, char *callback_name); WinPMail calls this function immediately on loading the extension, using the following parameters: "version" is passed in with the version of the WinPMail Extension Manager which is running. The major version of the Extension Manager is in the high byte and the minor version number is in the low byte. "variant" indicates what type of extension is required - the following values are currently defined: 0: Load an extension for composing a message ("Composer") 1: Load an extension for reading a message ("Reader") Having a "variant" field allows a single DLL to support multiple extension types, although you do not have to work this way. "hParent" contains the handle of the WinPMail MDI child window which is to contain the extension. If your extension does not create a dialog, you will need to store this value somewhere globally accessible, since you need it to communicate with the Extension Manager. If you create a dialog in your extension, you should create the dialog without any borders using the WS_CHILD window style and set its parent to be this window. "data" contains any string specified to be passed to the form by its Form Fact File. Think of this as a commandline for the extension - as an example, the PH extension expects to find its default host in this parameter. "hDialog" should be filled in with the window handle of the modeless dialog you create within the MDI child window, if any. If you set this value to NULL, then WinPMail will hide the MDI window associated with the extension and will route notification messages to the callback function (see the next entry). "callback_name" (optional) should be filled in with the name of the function in the DLL of the exported function to which messages should be sent or NULL if there is none. You will usually fill this entry in if you do not create a dialog so you can receive notifications from the Extension Manager. If a dialog is defined, the Extension Manager will always route messages to it, even if you enter a value in this field. The function whose name you return in this parameter (if you use it) should have the following prototype: DWORD PASCAL FAR _export form_fn (HWND hWnd, WORD wMsg, WPARAM wParam, LPARAM lParam); Notice that the prototype simply mirrors the definition for a standard Windows Window handler (WndProc) function. You've now done everything the Extension Manager requires to be able to use your extension - from here, only the good stuff is left for you to do - the actual working end of the extension, which is up to you. 5: Form Fact Files WinPMail learns about available extensions by looking for Form Fact Files, which are text files with the extension .FFF, describing the characteristics of the extension. WinPMail looks for FFF files first in the user's home mailbox, and then in the same directory as WINPMAIL.EXE, and finally (if present) in the directory pointed to by an FFF DOS environment variable. Scanning this way allows the use of both global and private extensions. Form Fact Files use a simple text format and are line based. Blank lines, and any line in a FFF which starts with #, ; or *, are all ignored as comments. Command lines in an FFF consist of a keyword sequence followed by optional whitespace, followed by the character '=', followed by more optional whitespace and a parameter. The following keyword sequences are recognized: Form name - The name the WinPMail Extensions Manager should display in the "Extensions" window. Form DLL - A required parameter which indicates the full path to the DLL WinPMail should load to access the extension. Standard command substitutions (as in user-defined gateways) can be used in this field - so for example, ~a/forms/foo.dll would be interpreted as "start in the directory where WINPMAIL.EXE resides, then change to the FORMS subdirectory and load FOO.DLL". For a complete list of command substitutions available, please see the file UDG.TXT supplied with Pegasus Mail. Form type - Should be either of the strings READER or COMPOSER depending on the type of extension being defined. A single definition can only have a single form type, although an FFF can contain multiple definitions. If absent, the default for this keyword is COMPOSER. Form flags - Contains settings which affect the operation of the extension. The value is an unsigned long (32 bits) which is treated as a bitmap. The following bit values are currently defined: Mnemonic Value Meaning ---------------------------------------------------------------- WPM_STARTUP 1 Load the extension when WinPMail starts up WPM_NOLIST 2 Do not show in the "Extensions" window list WPM_HIDDEN 4 Hide the parent MDI window on loading WPM_LOGGING 8 Extension wants to receive logging events WPM_ONEONLY 16 Only allow one running instance at any time Various flags can be combined by adding (or ORing) the values together - so to have a hidden extension which loads at startup, is interested in logging events, and does not appear in the "Extensions" window, you would use 1 + 2 + 4 + 8 = 15 Form tagname - This is the extension's unique identifier (see below for information on registering a tagname). WinPMail writes the tag name into any mail generated by the extension as an X- header - so, if your tagname was "FUBAR", WinPMail would write a header into the message which read "X-FUBAR: 1". Tagnames can be used as unique triggers for Reader extensions. Tagnames starting with the letters PM are reserved by the author. Form data - A "command line" for the extension. This text string is passed unmodified to the extension at load-time. The most normal use is to pass site-specific default information to the extension (for example, the Phone Message extension expects to see the default telephone area code in the "data" field). Form triggers - This keyword is only meaningful for READER extensions and is ignored if "Form Type = READER" has not already been seen. The parameter is an arbitrary regular expression which WinPMail should apply to the headers of a message to determine if the reader should be invoked for this message. The regular expression can contain '*' and '?' characters to match patterns within a header, and is presumed to start at the beginning of a line. So, for example, if you wanted to load a reader every time you saw a header called "X-File-type" which contained the letters "JPG", you might enter the trigger "X-FILE-TYPE*JPG*". Only a single trigger may be defined in any "Form Triggers" statement, but you may have as many "Form Triggers" lines in a definition as you wish. Override - An optional keyword with a single integer parameter; can only be used with COMPOSER extensions. This keyword indicates that the extension wishes to replace a built-in WinPMail function or menu selection. WinPMail checks for overriding extensions every time it processes a menu selection and if it finds one, it invokes the extension instead of the built-in function. An entry in an FFF can have only one "Override" line, but you can have multiple entries each defining a different override. In normal use, you will set the "Form Flags" line for an overriding extension to 18, so that only one instance can be open and it doesn't appear in the Extension Manager's list. For a list of menu IDs, see Appendix A at the end of this document. End - Indicates that the definition is complete; the entry is added to the internal extension list maintained by WinPMail. You MUST have an END statement for each definition - reaching end of file without an End statement will result in the definition being discarded. You may have as many definitions as you wish or need in a single FFF - and in fact, for system-wide FFFs there is a slight performance gain in gathering all your definitions into a single file. Remember to use an END statement to close every definition though. 6: Using the Extensions Manager API WinPMail's Extensions Manager makes a set of over 70 functions available to extensions, providing every imaginable kind of service from sending mail messages through to TCP/IP communications. An extension accesses the Extensions Manager API by using the Windows SendMessage function to send a message to its parent window. The parameters to SendMessage depend on the service required, and are fully described in WPMFORMS.H - you should consult that file as a definitive reference. With one or two specific exceptions (most notably the WM_F_GETMESSAGEFIELD message), the Extension Manager API messages can be sent at any time while the extension is loaded, including times when WinPMail is in the background. 7: Hints and tips Configuration files: if your extension needs to create a configuration file, the following approach may be helpful: a) Obtain the user's home mailbox location using WM_F_GETHOMEBOX b) Construct a filename using your extension's registered Tagname and the extension .PM. c) Write whatever information you need to keep into the file. You can use the same steps to locate a configuration file. Using your registered Tagname guarantees uniqueness for the filename, while using the extension .PM lets Pegasus Mail maintain the location of the file correctly for you even if the user shifts his/her home mailbox location. Disabling WinPMail features: Many sites with student populations have expressed the desire to be able to disable or regulate some of WinPMail's built-in features. You can do this with extensions either by creating a logging extension which looks for events concerning the features you want to disable (the most likely is WLOG_LOADMENU, which will allow you to disable the items in their menus), or else by creating extensions which override the menu choice for the target functions and put up an explana- tory dialog instead. 8: Accessing the Extension Manager from outside WinPMail On occasions you may want to access WinPMail's Extension Manager services from outside WinPMail itself; an example of such a situation might be the implementation of a MAPI interface which is loaded by other programs - in fact, WinPMail's own MAPI interface, which is in development at the time of writing, uses exactly this approach. To access the Extensions Manager in this way, locate the WinPMail frame window (which has the class FRAME and the title "Pegasus Mail") and send it a WM_INTERFACE message (#define WM_INTERFACE (WM_USER + 17480)). If you have a window which you want to receive WinPMail's WM_FM_* Extensions Manager notification messages, pass it in wParam. WinPMail will return the HWND of a new, hidden Extension Manager window to which your code can send messages requesting service just as if it were a standard extension. When you have finished with the service, send the window a WM_CLOSE message. ** NOTE: Issues of memory handling are up to your module; there may be ** instances where you will have to allocate global memory before passing ** it to WinPMail, usually if you have to take memory allocated by another ** program and make it available to WinPMail. The following code sample shows how to open an external connection to the Extensions Manager in the manner described above: #define WM_INTERFACE (WM_USER + 17480) HWND access_extension_service (HWND message_handler) { HWND hFrame; if ((hFrame = FindWindow ("FRAME", "Pegasus Mail")) == NULL) return NULL; return (HWND) SendMessage (hFrame, WM_INTERFACE, (HWND) message_handler, 0); } XX: Distributing extensions and reserving tagnames Because extensions are distributed as working, executable machine code, many sites may be reluctant to use an extension when they are unaware of its pedigree. If you have an extension you think would be useful and would like to see it widely distributed to the Pegasus Mail user community in a verified, secure manner, then you can follow these steps: * Debug and test your extension thoroughly until you are sure it works. This step is very important! The author does not provide a debugging service for your extensions and will reject out-of-hand anything which does not compile correctly or which crashes when run. * Contact the author of Pegasus Mail, David Harris, at the Internet e-mail address David.Harris@pmail.gen.nz. He will arrange for you to upload a private copy of your extension to his system and will examine it for certification. * You MUST upload source code to your extension. The author will only certify extensions which he has personally compiled, and when distributing them will only distribute binary versions which he has built. You are not required to distribute the source code with the extension itself, but it must be made available to him. * If your extension checks out OK and builds correctly, it will be made available on the principal Pegasus Mail distribution sites and endorsed as "safe and authenticated" by the author. * For technical reasons, the author can only certify extensions which are written for Borland C++ version 3.1 or later. Please make sure you include the appropriate make, PRJ or IDE file, as well as all associated RC, H and C or CPP files. Reserving extension tag names: because extensions can define tags for themselves which can be used to invoke a reader extension on receipt, it is obviously desirable to ensure that tagnames remain unique. In order to encourage this, the author offers a free tagname registration service. Send a mail message to "tagnames@pmail.gen.nz" indicating the tagname(s) you would like to reserve and optionally a brief description of your extension (for the author's interest). Tagnames will be registered on a first-come/ first-served basis, and if a tagname you have requested is already taken, you will be advised as soon as possible. The author reserves the right to refuse to register tagnames for any reasons he deems fit - for example, tagnames containing obscene words will not be accepted, nor will any tagname starting with the letters "PM", because all such tagnames are reserved by the author. The list of currently- registered extension tagnames can be retrieved by e-mail at any time by sending a message to maiser@pmail.gen.nz consisting of the two lines: SEND TAGNAMES.TXT EXIT Please note that tagnames can be a maximum of 12 characters long, may not contain characters with ASCII values lower than 33 or higher than 126 and are case-insensitive. All form names starting with the letters "PM" are reserved by the author. Appendix A: Menu IDs for overriding extensions The following integer values can be used as the parameter to an "Override" keyword line in a Form Fact File by extensions wishing to override particular built-in menu choices within WinPMail. ------------------------------------------------------------------------ WinPMail Mnemonic ID Menu function (Which Menu) ------------------------------------------------------------------------ IDM_HELP 10 "Context-sensitive help" (HELP) IDM_USING_HELP 11 "Using Help" (HELP) IDM_HELPINDEX 12 "Help index" (HELP) IDM_RMACRO 75 "Open glossary manager" (EDIT/GLOSSARY) IDM_PMACRO 76 "Expand glossary entry" (EDIT/GLOSSARY) IDM_NEWMESSAGE 101 "Start new message" (FILE) IDM_READNEW 102 "Open new mail folder" (FILE) IDM_OPENFOLDER 103 "Folders..." (FILE) IDM_IMPORT 104 "Import into message" (MESSAGE) IDM_SAVESESSION 105 "Save message as draft" (MESSAGE) IDM_RESTORESESSION 106 "Open saved message" (FILE) IDM_PRINT 107 "Print" (FILE) IDM_PRINTSETUP 109 "Printer setup" (FILE) IDM_FILTERS 110 "New mail filtering rules" (FILE) IDM_EXIT 111 "Exit" (FILE) IDM_PREF_GENERAL 121 "General settings" (FILE/PREFS) IDM_PREF_SMF 122 "NetWare MHS settings" (FILE/PREFS) IDM_PREF_SIGS 123 "Signatures..." (FILE/PREFS) IDM_PREF_HOMEBOX 124 "Home mailbox location" (FILE/PREFS) IDM_PREF_EXT 125 "Extended features" (FILE/PREFS) IDM_UNDO 131 "Undo" (EDIT) IDM_CUT 132 "Cut" (EDIT) IDM_COPY 133 "Copy" (EDIT) IDM_PASTE 134 "Paste" (EDIT) IDM_CLEAR 135 "Clear" (EDIT) IDM_SELECTALL 136 "Select all" (EDIT) IDM_SPELLING 137 "Check spelling" (EDIT) IDM_SERVER 138 "File servers..." (FILE) IDM_LOCALUSERS 141 "Lookup local users" (ADDRESSES) IDM_ADDRESSBOOKS 142 "Address books" (ADDRESSES) IDM_MAILINGLISTS 143 "Distribution lists..." (ADDRESSES) IDM_SORTBYKEY 144 "Sort by key" (ADDRESSBOOK) IDM_SORTBYNAME 145 "Sort by name" (ADDRESSBOOK) IDM_FIND 146 "Find text", (MESSAGE/FOLDER/READER/ADDRBK) IDM_FINDAGAIN 147 "Find again", (MESSAGE/FOLDER/READER/ADDRBK) IDM_PASTENAMES 148 "Paste names" (ADDRESSBOOK) IDM_PASTEADDRESSES 149 "Paste addresses" (ADDRESSBOOK) IDM_SAVESIZE 150 "Save window size" (Various) IDM_OTHEROPTIONS 151 "Special message view" (MESSAGE) IDM_SHOWHEADERS 153 "Show all headers" (READER) IDM_EXTRACT 154 "Save messages to disk" (FOLDER/READER) IDM_ATTACH 155 "Switch to attachments view" (MESSAGE) IDM_MARKUNREAD 156 "Mark message as unread" (FOLDER and NB) IDM_ALIASING 157 "Resolve aliases" (ADDRESSBOOK) IDM_BPPREF 158 "Button panel preferences" (FILE/PREFS) IDM_UUDECODE 160 "UUdecode message" (FOLDER/READER /SPECIAL) IDM_UNBINHEX 161 "Un-BinHex message" (FOLDER/READER /SPECIAL) IDM_LOCALSIG 162 "Signature for local mail" (FILE/PREFS) IDM_INETSIG 163 "Signature for Internet mail" (FILE/PREFS) IDM_MHSSIG 164 "Signature for MHS mail" (FILE/PREFS) IDM_DELEXPIRED 165 "Delete expired messages" (FOLDER/SPECIAL) IDM_EDMAIL 167 "Switch to message editor view" (MESSAGE) IDM_QUICKLOOKUP 168 "Quick lookup" (ADDRESSES) IDM_WORDWRAP 169 "Wrap long lines" (READER) IDM_DELLINE 170 "Delete line" (MESSAGE) IDM_REFORMAT 171 "Reformat paragraph" (MESSAGE) IDM_INDENT 172 "Indent text" (MESSAGE) IDM_REPLACE 173 "Search and replace" (MESSAGE) IDM_DELWORD 174 "Delete word right" (MESSAGE) IDM_APREFS 175 "Advanced settings" (FILE/PREFS) IDM_ENLARGE 176 "Enlarge window" (WINDOW) IDM_ABK_EXPORT 177 "Export addressbook" (ADDRESSBOOK) IDM_ABK_IMPORT 178 "Import addressbook" (ADDRESSBOOK) IDM_NOTICEBOARDS 179 "Noticeboards" (FILE) IDM_ANN_EDIT 180 "Add/edit annotation" (FOLDER) IDM_SRT_DATE 181 "Sort by date" (FOLDER) IDM_SRT_REVDATE 182 "Sort by reverse date" (FOLDER) IDM_SRT_SUBJECT 183 "Sort by subject" (FOLDER) IDM_SRT_FROM 184 "Sort by sender's name" (FOLDER) IDM_FORMS 185 "Open Forms Manager" (FILE) IDM_ANN_DELETE 186 "Delete annotation" (FOLDER) IDM_REVIEWMAIL 187 "Review Queued Mail" (FILE) IDM_TODISK 230 "Save to disk" (Various) IDM_FROMDISK 231 "Read from disk" (Various) IDM_EXTRACTFILE 240 "MHS directory service" (ADDRESSES) IDM_LOGGED_IN 241 "Logged-in users" (ADDRESSES) IDM_ADD_USER 242 "Add sender to list" (READER/SPECIAL) IDM_REMOVE_USER 243 "Remove sender from list" (READER/SPECIAL) IDM_FONT 299 "Font" (Various) IDM_TILE 500 "Tile" (WINDOW) IDM_CASCADE 501 "Cascade" (WINDOW) IDM_ARRANGEICONS 502 "Arrange Icons" (WINDOW) IDM_BUTTONPANEL 503 "Button panel" (WINDOW)