Palm Developer Tips & Tricks |
||
After a few years of Palm software development, I have learned a few tips and tricks that may be useful for other developers. As a matter of fact, I know some of them are helpful, since I sometimes receive direct requests about some feature in my softs. This is how I came with the idea for this page. I intend to use this space to present the most interesting tips I have collected, in the form of short information, sample sources or anything in between. This will start with a single tip, one for which I receive quite a number of queries, but I expect the page to grow with time. Let me know if you have specific requests ;-) Since I write only C code (with good old prc-tools and pilrc), the occasional sample source will be in this language. However, most of the tips I will discuss here may be used with any language and development tool. |
Quick Connect, "Map" option |
The Palm Tungsten T (and later devices, except the Treo series) comes with an enhanced version of the address book which offers a feature named "Quick Connect". This feature allows to open another application with the context of the contact on which it was launched. The standard configuration lets you easily place a call, send an SMS or a mail to the contact you have selected. This feature is based on simple helper notifications, well documented in the PalmOS developer guides (for example, check this chapter in the PalmOS Companion guide). I will not enter in the details of this API and I assume you have understood how it works. However you may have noticed that there is an option in Quick Connect, named "Map", which is not documented at all. I will give you the missing information, which I used to build "A2Map", a small utility that "Quick Connects" to a ViaMichelin navigation software and centers the selected contact address location on the map. The first piece of information you need to implement a helper for the "Map" service is its "service class": 'mapH' (as for all service classes, this is an unsigned long integer). The second piece of information required (and much more difficult to find out) is the data your helper receives when the user tries to "Quick Connect". Here is the C structure type of the "detailsP" parameter coming with the notification: typedef struct { UInt16 version; UInt32 recid; Char * address; Char * city; Char * state; Char * zipcode; Char * country; } HelperServiceMapDetailsType; and a few explanations:
|
Writing a DA (Desk Accessory) |
Let's assume you know what a DA is. If not, check this page first. Now, if you came here, it's because you want to create your own DA. There used to be a lot of resources about the subject but, since the Japanese have more or less given up their Palms, the web sites are closing down one after the other (yes, the DA concept originated in Japan and most of the apps as well). Although what I will discuss here is applicable to any language, the sample code and the "recipes" are designed for C developers (and preferably those using prc-tools). For users of PP Compiler, there is another source of information with a sample written in their favorite language. As a matter of fact, my sample is directly derived from this one (Thanks, Palmipod ;-). Before reading the explanations, you may download the source for the sample. Before you start building your DA from this sample, remember you need a CreatorID as for any PalmOS software. You may set your own ID in the makefile Now, let's begin with the core of the matter. A DA is a piece of executable code that differs from a regular program on the following points:
You're still with me? OK, let's see how this translates in code. 1) First the resource type: setting the type is easily done with a '-t DAcc' parameter in the 'build-prc' command. 2) While we are in the makefile, let's take care of the absence of start-up code and the specific code resource ID: a '-nostartfiles' parameter in the link command will take care of the first and a '--no-check-resources' in the 'build-prc' commmand ensures the tool will not complain about the missing standard resource. 3) The last change we have to implement in the makefile is about defining the 'entry point'. In our sample, it will be a function named 'DAMain'. We specify this to the linker with a '-e DAMain' parameter. Let's summarize this with a complete pair of link and build commands: $(CC) -nostartfiles -e DAMain $(CFLAGS) -o $@ $(OBJS) $(BUILDPRC) --no-check-resources -o $@ -n $(ICONTEXT) -c $(APPID) -t DAcc SampleDA SampleDA.ro The rest of the makefile is pretty standard. 4) Next step: just a quick look at the form resource (in SampleDA.rcp): FORM ID ID_FORM_MAIN AT (30 30 100 80) MODAL SAVEBEHIND USABLE ... 5) Now let's see the C code itself (in SampleDA.c). We will start with changing the code resource ID to 1000. For this we will use the 'STANDALONE_CODE_RESOURCE_ID' macro, defined in 'standalone.h' (this is a standard prc-tools header file). At the top of your source, you then insert: #include <standalone.h> STANDALONE_CODE_RESOURCE_ID(1000); And now, we have to write the code for the entry point, which is a simple function without parameter and returning nothing. To make things clearer, I have split the whole process in separate functions: void DAMain(void) { FormPtr form = NULL; // Start application StartApplication(&form); // Test if OK if (form != NULL) { // Run EventLoop(); // Stop StopApplication(form); } } The function 'StartApplication()' will initialize and display the main form: static void StartApplication(FormPtr * form) { // Load the form resource. *form = FrmInitForm(ID_FORM_MAIN); // Check if OK if (*form) { // Set as active form FrmSetActiveForm(*form); // Display form FrmDrawForm(*form); } } The function 'EventLoop()' manages the actual event loop: static void EventLoop(void) { EventType event; Boolean done = false; do { // Wait next event EvtGetEvent(&event, evtWaitForever); // System event processing & system form events handler if (!SysHandleEvent(&event) && !FrmHandleEvent(FrmGetActiveForm(), &event)) { // Local form handler done = MainFormHandleEvent(&event); } } while (!done); } (Note that in the sample, there is no menu and the event loop has no provision to handle menu events) And the final function 'StopApplication()' takes care of cleaning up after the user quits the DA: static void StopApplication(FormPtr form) { // Remove form from screen FrmEraseForm(form); // Erase form data from memory FrmDeleteForm(form); } As you may have noticed, the form handler (MainFormHandleEvent in the example) is called directly from the event loop and it must return 'true' when the user quits. The sample source includes the code that let the user quit the DA with a stylus tap outside the main window (check the 'penDownEvent' event processing). The original "Desk Accessory Specification" is located here |
Playing with the network services |
OK, this is not something you are likely to do every day, but when you start looking for information on such things as listing the network services, querying or changing the default services, you will realize this is a challenge... I am not talking about creating or modifying a service configuration: there are quite a few sources of information for this out there. I stumbled upon the problem for a few days, while planning for NetSelDA, when I finally found what I was looking for in an old post by Gavin Peacock. And everything suddenly becomes simple! You just have to query the network panel for the services need... And indeed, you can find these lines in the standard SystemMgr.h header: // Standard Service Panel launch codes (used by network panel, dialer panel, etc.) #define sysSvcLaunchCmdSetServiceID 20 #define sysSvcLaunchCmdGetServiceID 21 #define sysSvcLaunchCmdGetServiceList 22 #define sysSvcLaunchCmdGetServiceInfo 23 What was missing though, is a description of the data we should exchange with the network panel... Gavin gives us the answers. The 'sysSvcLaunchCmdSetServiceID' & 'sysSvcLaunchCmdGetServiceID' launch codes both expect a pointer to a 'ServiceIDType' as a parameter: typedef UInt32 ServiceIDType; The 'sysSvcLaunchCmdGetServiceInfo' code (which returns a service name given its ID) expects a pointer to a 'serviceInfoType' structure: #define maxServiceNameLen 32 // Max length of a service name typedef struct { Err error; // Error code ServiceIDType serviceID; // ID of service to query (in) UInt32 reserved; // Reserved - set to 0 Char serviceName[maxServiceNameLen]; // Name of service (out) } serviceInfoType, * serviceInfoPtr; And the 'sysSvcLaunchCmdGetServiceList' code, expects a pointer to a 'serviceListType' structure: typedef struct { Err error; // Error code UInt16 numServices; // Number of services available MemHandle IDListH; // MemHandle to an array of serviceIDs MemHandle nameListH; // MemHandle to an array of service names } serviceListType, * serviceListPtr; On return, the 'IDListH' and 'nameListH' handles contain a list of service IDs and names, respectively. The handles are allocated by the network panel but it is the caller's responsibility to dispose of them when done. Now, let's use this in an example: we will query the list of available services and display them in a simple UI list (with an ID of 'ID_LIST_NETC'). Note that the code is provided as an example, you should assemble the parts yourself (including error checking, memory release, ...). 1) We need to launch the network panel and to do so, we have to find its 'card number' and 'db ID': static Boolean FindNetworkPanel(UInt16 * card, LocalID * dbid) { DmSearchStateType searchState; // Find card number and db id for network panel if (DmGetNextDatabaseByTypeCreator(true, &searchState, sysFileTPanel, sysFileCNetworkPanel, true, card, dbid) == errNone) { return true; } // Error return false; } 2) Then we can send a query to the network panel. This is the interesting part. You would do about the same for querying or setting the current default service or to get information about a service. static Boolean GetServiceList(UInt16 card, LocalID dbid, serviceListType * srvList) { UInt32 result; // Request a list of services if (SysAppLaunch(card, dbid, 0, sysSvcLaunchCmdGetServiceList, (MemPtr) &srvList, &result) == errNone) { // Any service returned if (pNetSelData -> srvList.numServices > 0) { // OK return true; } } // Error return false; } 3) And finally, to display the list (this part is rather straightforward...): static void FillUIServiceList(serviceListType * srvList, ListPtr list) { // Lock the name list handle Char * srvNameList = (Char *) MemHandleLock(srvList -> nameListH); // Create an array for the UI list MemHandle srvNameArray = SysFormPointerArrayToStrings(srvNameList, srvList -> numServices); // Lock handle Char ** srvNames = (Char **) MemHandleLock(srvNameArray); // Fill UI list LstSetListChoices(list, srvNames, srvList -> numServices); } |
A simple HTTP library |
Network applications are becoming quite common these days, particularly over HTTP, and recent devices from Palm come with a high-level HTTP library. However, if you want to write an HTTP-aware program supporting older PalmOS versions, you have to write everything from scratch. I encountered this problem in 2002 and came accross a library by Laurent Demailly that could help. Since it was released as open source and I needed some enhancements, I have made some changes to the original code, mainly to make a regular PalmOS shared library and to support HTTP 1.1. You may download the complete source and executable package here. Just as the original work from Laurent, this is released under the Artistic License. Please check the 'readme.txt' file before doing anything. Here, I won't enter the details of the sources of the library itself but I am going to show you how you may use the library from your own program. For this, we will proceed through the steps required to read a page from a web server, through the 'GET' method. First, you must include the library header in your source: #include "httplib.h" You can then open the library: UInt16 HTTPLibRef; Err err; // Find the library if it's already loaded err = SysLibFind(HTTPLibName, &HTTPLibRef); // Not found if (err != errnone) { // Load the library err = SysLibLoad(sysFileTLibrary, HTTPLibCreator, &HTTPLibRef); } // We have a reference to the library if (err == errNone) { // Open it err = HTTPLibOpen(HTTPLibRef); } // The library is open if (err == errNone) { // Open the Net Library // At this stage, the device will try to open the default network service err = HTTPLibNetOpen(HTTPLibRef); } // Something went wrong if (err) { // Process errors... FrmAlert(LoadLibraryAlert); return err; } And when it's time to send your request to the server, just run the 'HTTPLibGet' function: err = HTTPLibGet(HTTPLibRef, &ret, theURL, &data, &len, type); Where:
Notes:
When you are done with the library, there is some clean-up to do: Err err; Int16 usecount; // Free memory allocated by the library if (data) { MemPtrFree(data); } // Close the net library HTTPLibNetClose(HTTPLibRef); // Close HTTP library err = HTTPLibClose(HTTPLibRef, &usecount); // If closed and not used anymore if (err == errNone && usecount == 0) { // Remove from memory SysLibRemove(HTTPLibRef); } |
Creating a preference panel |
Implementing a program in the "Preferences" of the PalmOS is not very complex but to do it cleanly requires some care. Since there is no point reinventing the wheel, I have packaged most of what is needed in a pair of source files (one header file utilpanl.h and the actual C source utilpanl.c), that you may download freely. And the rest of this article will explain how to use this material. Let's start with a bit of theory about the PalmOS preferences and a "preferences panel":
Now let's put this in practice. First, you must include the utilpanl.c code in your build (compilation & link). Setting the resource type for the program is easily done with a '-t panl' parameter in the 'build-prc' command: $(BUILDPRC) -o $@ -n $(ICONTEXT) -c $(APPID) -t panl MyPref MyPref.ro The various mandatory UI elements required in all versions of a preferences panel are included in this resource template: FORM ID ID_FORM_MAIN AT (0 0 160 160) NOFRAME NOSAVEBEHIND USABLE DEFAULTBTNID ID_BUTN_CLOS BEGIN TITLE "APPNM" POPUPTRIGGER "" ID ID_TRIG_PANL AT (RIGHT@159 1 AUTO AUTO) USABLE RIGHTANCHOR LIST "" ID ID_LIST_PANL AT (PREVLEFT PREVTOP 55 AUTO) NONUSABLE VISIBLEITEMS 1 POPUPLIST ID ID_TRIG_PANL ID_LIST_PANL LABEL "APPNM" ID ID_LABL_TITL AT (RIGHT@159 1) NONUSABLE /* Insert your own controls here */ BUTTON "BTEND" ID ID_BUTN_CLOS AT (2 144 AUTO AUTO) NONUSABLE FRAME END where:
The code for your application is not very different from a standard one. In the PilotMain() function, you have to process some specific launch codes (used with the "original" Preferences application): UInt32 PilotMain(UInt16 cmd, void * cmdPBP, UInt16 launchFlags) { // Test launch code switch (cmd) { // Process normal launch & preferences launch case sysAppLaunchCmdNormalLaunch: case sysAppLaunchCmdPanelCalledFromApp: case sysAppLaunchCmdReturnFromPanel: // Store call mode and save current panel PanelSetCallMode(cmd == sysAppLaunchCmdPanelCalledFromApp, myCreatorID); // Proceed with your normal event loop /* !!! Your code here !!! */ break; } } The call to PanelSetCallMode is used to store the launch mode for future use (through a global variable, which is not very nice but it's the only way to make this work with an autonomous utility package). It also saves your panel as the current one in the Palm preferences, so that the next time the preferences are opened, it will be on that same panel. For this to work, you must replace the myCreatorID with your own creator ID (as an UIn32 of course). The actual application processing is done after and should include the opening of the main form: FrmGotoForm(ID_FORM_MAIN); as well as a regular event loop. All that is left to do is some specific "preferences" tasks handling in your main form handler. Here is a skeleton for the handler: static Boolean MainFormHandleEvent(EventType * event) { Boolean handled = false; switch (event->eType) { case frmOpenEvent: // Initialize pref panel PanelFormInit(FrmGetActiveForm()); // Draw form content FrmDrawForm(form); handled = true; break; case ctlSelectEvent: // Close button handled = PanelSelectClose(event); // Other controls if (!handled) { } break; case popSelectEvent: handled = PanelSelectPanel(event); break; case frmCloseEvent: PanelFormClose(); break; } return handled; } Of course, for each of these events, you may also add your own processing... If you are interested, here are a few explanations about this code:
|
Copyright © 1997-2009 Kinevia SARL. All rights reserved. Last update by Patrice BERNARD on 03/29/2009 |
Métro... |