/****************************************************************************** * * Copyright (c) 1994-2004 PalmSource, Inc. All rights reserved. * * File: Whiteboard.c * * Release: Palm OS 6.0.1 * * Description: * Bluetooth Whiteboard sample application * *****************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //#include #include "WhiteboardRsc.h" // #### SysTicksPerSecond not yet defined #ifndef SysTicksPerSecond #define SysTicksPerSecond() (1000) #endif #define CAN_POLL_FD_0 true // does polling fd 0 for UI events work? #define UNUSED_ARG(x) (x) // To avoid compiler warning about unused arguments // Some macros to manage byte ordering #if (CPU_ENDIAN == CPU_ENDIAN_BIG) // convert network int16_t to host int16_t #define NToHS(x) (x) // convert host int16_t to network int16_t #define HToNS(x) (x) #elif (CPU_ENDIAN == CPU_ENDIAN_LITTLE) // convert network int16_t to host int16_t #define NToHS(x) _BtLibNetSwap16(x) // convert host int16_t to network int16_t #define HToNS(x) _BtLibNetSwap16(x) #else #error "No CPU_ENDIAN defined" #endif /*********************************************************************** * Constants ***********************************************************************/ #define kAppFileCreator 'btwb' // register your own at http://www.palmos.com/dev/creatorid/ #define kAppBtName "Whiteboard Piconet Example Application" #define kAppWhiteBoardErrorClass appErrorClass + 5 #define kIOsFdNull -1 #define kL2CapSocketMtu 500 #define kMaxDevices 7 #define kBdAddrStrSize 18 #define kQueueSendTriggerSecs 1 #define kMaxQueueSize 100 // {83E087AE-4E18-46be-83E0-7B3DE6A1C33B} static const BtLibSdpUuidType kWhiteboardUUID = {btLibUuidSize128, { 0x83, 0xe0, 0x87, 0xae, 0x4e, 0x18, 0x46, 0xbe, 0x83, 0xe0, 0x7b, 0x3d, 0xe6, 0xa1, 0xc3, 0x3b } }; static const BtLibClassOfDeviceType kDeviceFilter = (btLibCOD_ServiceAny | btLibCOD_Major_Computer | btLibCOD_Minor_Comp_Palm); static const char kLockStr[] = "Lock"; static const char kUnlockStr[] = "Unlock"; static const RectangleType kDrawRect = {0,0,160,143}; // Draw area #define kMyMtu 100 /*********************************************************************** * Structures ***********************************************************************/ typedef struct _WhiteBoardRemoteDrawEvent { Boolean clear; //if clear is set, lines values are meaningless PointType lineStart; PointType lineStop; } WhiteBoardRemoteDrawEvent; typedef struct _BtDeviceInfoType { BtLibDeviceAddressType bdAddr; BtLibSocketRef connectionSocket; } BtDeviceInfoType; typedef struct _DeviceAddList { BtLibDeviceAddressType selectedDevices[kMaxDevices]; uint8_t numSelectedDevices; } DeviceAddList; /*********************************************************************** * Entry Points ***********************************************************************/ uint32_t PilotMain(uint16_t cmd, MemPtr cmdPBP, uint16_t launchFlags); /*********************************************************************** * Global variables ***********************************************************************/ static DmOpenRef gDbResId; static Boolean gIsMaster; static uint16_t gSavedAutoOff; static BtLibSdpRecordHandle gSdpRecordH = 0; static BtDeviceInfoType gDevices[kMaxDevices] ={ {{0}, kIOsFdNull}, {{0}, kIOsFdNull}, {{0}, kIOsFdNull}, {{0}, kIOsFdNull}, {{0}, kIOsFdNull}, {{0}, kIOsFdNull}, {{0}, kIOsFdNull} }; static uint16_t gNumDevices = 0; static uint16_t gNumConnections = 0; static uint8_t gPendingLinks = 0; // For adding devices to the piconet static DeviceAddList gSelectedDevicesList = { {{0},{0},{0},{0},{0},{0}, {0}}, 0}; static char gBdAddrStr[kBdAddrStrSize]; /* WhiteBoard Data */ static PointType gLastLocalPenLocation = {0,0}; static Boolean gPenIsDown = false; static uint32_t gLastPenMoveTicks = 0; static WhiteBoardRemoteDrawEvent gRemoteDrawingQueue[kMaxQueueSize]; //the queue of events to send static uint16_t gRemoteQueueFisrtIndex = 0; static uint16_t gRemoteQueueLength = 0; static Boolean gPendingQueueSend = false; static WinHandle gOffscreenWinH = 0; // Draw to this window so data is stored off screen static char gStatus[100]; // String to store status information for display static uint32_t gTimeToClearError = 0; // Errors are cleared after some elapsed period // A PollBox is a collection of file descriptors, for polling. // static PollBox* gPollBox; static int32_t gFdUI = kIOsFdNull; // fd for UI events static int32_t gFdME = kIOsFdNull; // fd to Management Entity device static int32_t gFdListener = kIOsFdNull; // fd to L2CAP or RfComm device, for listening static Boolean gStopFlag = false; /*********************************************************************** * Internal Functions ***********************************************************************/ static Boolean MainFormHandleEvent(EventPtr eventP); static Boolean DrawingFormHandleEvent(EventPtr eventP); static status_t BluetoothInit(void); static void BluetoothCleanup(void); static Boolean L2CapValidChannel(void); static Boolean L2CapSendPending(void); static status_t L2CapConnect(uint8_t index, BtLibL2CapPsmType psm); static status_t L2CapFindPsm(void); static void L2CapDisconnect(BtLibSocketRef socket); static void DiscoverAndAdd(void); static void AclCreateLinks(void); static void AclCreateNextLink(void); static void SocketHandleDisconnect(BtLibDeviceAddressTypePtr bdAddr); static void DestroyPiconet(void); static Boolean AddDrawEventToQueue(uint8_t clear, uint16_t startX, uint16_t startY, uint16_t endX, uint16_t endY); static int16_t IndexOfHeadOfQueue(void); static Boolean RemoveHeadOfQueue(uint16_t num); static void ResetQueue(void); static uint16_t AttemptToSendNext_N_EnquedEvents(uint16_t num); static void AttemptQueueSend(void); static void OffscreenDrawLine(Coord x1, Coord y1, Coord x2, Coord y2, uint16_t newForeColor); static void ClearDrawArea(void); static uint8_t FindDeviceIndex(BtLibDeviceAddressTypePtr bdAddrP); static uint8_t FindSocketIndex(BtLibSocketRef socket); static void ChangeLabel( uint16_t labelID, const char *newLabel); static void UpdateStatusDisplay(void); static void StatusMsg(const char *formatStr, ...); static void ErrorMsg(const char *formatStr, ...); static void HandlePbxMeEvent( struct PollBox* pbx, struct pollfd* pollFd, void* context); static void HandlePbxSocketEvent( struct PollBox* pbx, struct pollfd* pollFd, void* context ); #define min(a, b) (((a) < (b)) ? (a) : (b)) /*********************************************************************** * * FUNCTION: MainFormHandleEvent * * DESCRIPTION: Event handler of the main form * * PARAMETERS: The event * * RETURNED: true if event was handled * ***********************************************************************/ static Boolean MainFormHandleEvent(EventPtr eventP) { Boolean handled = true; FormPtr frmP; status_t error; switch (eventP->eType) { case menuEvent: if (eventP->data.menu.itemID == MainOptionsAboutWhiteboard) { MenuEraseStatus(0); // Clear the menu status from the display. AbtShowAbout(kAppFileCreator); } break; case ctlSelectEvent: switch (eventP->data.ctlSelect.controlID) { case MainHostButton: gIsMaster = true; error = BtLibPiconetCreate(gFdME, true, true); if(!error) FrmGotoForm(gDbResId, DrawingForm); else ErrorMsg("Failed to create piconet"); break; case MainConnectButton: gIsMaster = false; error = BtLibDiscoverDevices(gFdME, NULL, NULL, false, (BtLibClassOfDeviceType*)&kDeviceFilter, 1, false, &(gSelectedDevicesList.selectedDevices[0]), 1, NULL); if( !error) { gSelectedDevicesList.numSelectedDevices = 1; AclCreateLinks(); } else ErrorMsg("Discovery result: 0x%hx", error); break; } break; case frmOpenEvent: frmP = FrmGetActiveForm(); FrmDrawForm (frmP); gIsMaster = false; break; default: handled = false; } return handled; } /*********************************************************************** * * FUNCTION: DrawingFormHandleEvent * * DESCRIPTION: Event handler of the drawing form * * PARAMETERS: The event * * RETURNED: true if event was handled * ***********************************************************************/ static Boolean DrawingFormHandleEvent(EventPtr eventP) { FormPtr frmP = FrmGetActiveForm(); ControlPtr ctrlP; Boolean handled = true; status_t error; switch (eventP->eType) { case nilEvent: if (L2CapValidChannel()) { uint32_t timeSinceLastMove =(uint32_t)((TimGetTicks() - gLastPenMoveTicks)/SysTicksPerSecond()); if (gPenIsDown && timeSinceLastMove >= kQueueSendTriggerSecs) gPendingQueueSend = true; if (gPendingQueueSend) AttemptQueueSend(); } handled = false; break; case penDownEvent: if (L2CapValidChannel()) { gPenIsDown = true; gLastLocalPenLocation.x = eventP->screenX; gLastLocalPenLocation.y = eventP->screenY; } handled = false; break; case penUpEvent: if (L2CapValidChannel()) { gPenIsDown = false; AttemptQueueSend(); } handled = false; break; case penMoveEvent: if ( L2CapValidChannel() && gOffscreenWinH && eventP->screenY < kDrawRect.topLeft.y + kDrawRect.extent.y) { gLastPenMoveTicks = (uint32_t)TimGetTicks(); OffscreenDrawLine(gLastLocalPenLocation.x, gLastLocalPenLocation.y, eventP->screenX, eventP->screenY, 0); AddDrawEventToQueue( false, gLastLocalPenLocation.x, gLastLocalPenLocation.y, eventP->screenX, eventP->screenY); AttemptQueueSend(); gLastLocalPenLocation.x = eventP->screenX; gLastLocalPenLocation.y = eventP->screenY; } handled = false; break; case ctlSelectEvent: if(gPendingLinks != 0) break; // No UI interaction while adding devices switch (eventP->data.ctlSelect.controlID) { case DrawingClearScreenButton: ClearDrawArea(); AddDrawEventToQueue( true, 0,0,0,0); AttemptQueueSend(); break; case DrawingLeaveButton: if (gIsMaster) DestroyPiconet(); else L2CapDisconnect(gDevices[0].connectionSocket); break; case DrawingAddDevicesButton: DiscoverAndAdd(); break; case DrawingToggleLockButton: { ctrlP = FrmGetObjectPtr(frmP, FrmGetObjectIndex(frmP, DrawingToggleLockButton)); if(!StrCompare(CtlGetLabel(ctrlP), kLockStr)) BtLibPiconetLockInbound(gFdME); else BtLibPiconetUnlockInbound(gFdME, true); break; } default: handled = false; break; } break; case frmOpenEvent: if (!gIsMaster) { // We are a slave so no add or lock buttons FrmHideObject(frmP, FrmGetObjectIndex(frmP, DrawingAddDevicesButton)); FrmHideObject(frmP, FrmGetObjectIndex(frmP, DrawingToggleLockButton)); } gOffscreenWinH = WinCreateOffscreenWindow(kDrawRect.extent.x, kDrawRect.extent.y, nativeFormat, &error); if ((error) || (!gOffscreenWinH)) { TraceOutput(TL( kAppWhiteBoardErrorClass, "Could not create offscreen window (height=%d width=%d) error=0x%lX= ", kDrawRect.extent.x, kDrawRect.extent.y, error )); ErrFatalDisplay("Could not create offscreen window"); } ErrFatalDisplayIf(gOffscreenWinH == 0,"Could not create offscreen window"); FrmDrawForm (frmP); break; case frmCloseEvent: WinDeleteWindow(gOffscreenWinH, false); handled = false; break; case frmUpdateEvent: //Copy Offscreen window to the display FrmDrawForm(frmP); WinCopyRectangle(gOffscreenWinH, NULL, &kDrawRect, 0, 0, winPaint); break; default: handled = false; break; } return handled; } #if 0 #pragma mark - #endif /*********************************************************************** * * FUNCTION: BluetoothInit * * DESCRIPTION: Find/Load/Open the lib and setup our listener * * PARAMETERS: Nothing * * RETURNED: status_t value 0 if nothing went wrong * ***********************************************************************/ static status_t BluetoothInit(void) { status_t error = errNone; BtLibSocketListenInfoType listenInfo; TraceOutput(TL(kAppWhiteBoardErrorClass, "BluetoothInit:")); error = BtLibOpen(&gFdME); if (error && error != btLibErrAlreadyOpen) { TraceOutput(TL(kAppWhiteBoardErrorClass, "BluetoothInit: BtLibOpen() error=0x%lX", error)); return error; } error = PbxAddFd( gPollBox, gFdME, POLLIN, HandlePbxMeEvent, 0 ); if (error) { TraceOutput(TL(kAppWhiteBoardErrorClass, "BluetoothInit: PbxAddFd(fdME) error=0x%lX", error)); return error; } error = BtLibSocketCreate( &gFdListener, btLibL2CapProtocol); if (error) { // EventType tempoEvent; TraceOutput(TL(kAppWhiteBoardErrorClass, "BluetoothInit: BtLibSocketCreate error=%lX", error)); /* tempoEvent.eType = frmCloseEvent; tempoEvent.data.frmOpen.formID = FrmGetActiveFormID(); (void) EvtAddEventToQueue(&tempoEvent); */ return error; } ErrFatalDisplayIf(error, "L2CAP Socket Creation Failure"); error = PbxAddFd( gPollBox, gFdListener, POLLIN, HandlePbxSocketEvent, &gFdListener ); if (error) { TraceOutput(TL(kAppWhiteBoardErrorClass, "BluetoothInit: PbxAddFd(gFdListener) error=%lX", error)); return error; } listenInfo.data.L2Cap.localPsm = BT_L2CAP_RANDOM_PSM; listenInfo.data.L2Cap.localMtu = kL2CapSocketMtu; listenInfo.data.L2Cap.minRemoteMtu = kL2CapSocketMtu; error = BtLibSocketListen( gFdListener, &listenInfo); if (!error) { error = BtLibSdpServiceRecordCreate(gFdME, &gSdpRecordH ); if (!error) { status_t err = errNone; err = BtLibSdpServiceRecordSetAttributesForSocket( gFdListener, (BtLibSdpUuidType*) &kWhiteboardUUID, 1, kAppBtName, strlen(kAppBtName), gSdpRecordH); if (err) TraceOutput(TL(kAppWhiteBoardErrorClass, "BluetoothInit: BtLibSdpServiceRecordSetAttributesForSocket(gFdListener) error=%lX", err)); err = BtLibSdpServiceRecordStartAdvertising(gFdME, gSdpRecordH); if (err) TraceOutput(TL(kAppWhiteBoardErrorClass, "BluetoothInit: BtLibSdpServiceRecordStartAdvertising(gFdME) error=%lX", err)); } else { TraceOutput(TL(kAppWhiteBoardErrorClass, "BluetoothInit: BtLibSdpServiceRecordCreate(gFdME) error=%lX", error)); } } else { TraceOutput(TL(kAppWhiteBoardErrorClass, "BluetoothInit: BtLibSocketListen(gFdListener) error=%lX", error)); } ErrFatalDisplayIf(error, "L2CAP Listen Failure"); return error; } /*********************************************************************** * * FUNCTION: BluetoothCleanup * * DESCRIPTION: Destroy SDP record, close listener socket, * unregister callback and close lib * * PARAMETERS: Nothing * * RETURNED: Err value 0 if nothing went wrong * ***********************************************************************/ static void BluetoothCleanup(void) { BtLibSdpServiceRecordDestroy(gFdME, gSdpRecordH); BtLibSocketClose(gFdListener); PbxRemoveFd( gPollBox, gFdListener ); BtLibClose(gFdME); } #if 0 #pragma mark - #endif /*********************************************************************** * * FUNCTION: L2CapValidChannel * * DESCRIPTION: Check that we are connected * * PARAMETERS: Nothing * * RETURNED: true if L2Cap channel is valid * ***********************************************************************/ static Boolean L2CapValidChannel(void) { BtLibL2CapChannelIdType channel; status_t error; error = BtLibSocketGetInfo(gDevices[0].connectionSocket, btLibSocketInfo_L2CapChannel, &channel, sizeof(channel)); return (error == btLibErrNoError); } /*********************************************************************** * * FUNCTION: L2CapSendPending * * DESCRIPTION: Check if a send operation is pending on any active connection * * PARAMETERS: Nothing * * RETURNED: true if a send operation is pending * ***********************************************************************/ static Boolean L2CapSendPending(void) { Boolean sending = false; uint16_t i = 0; for(i = 0; i < gNumDevices && gDevices[i].connectionSocket != kIOsFdNull; i++) { BtLibSocketGetInfo(gDevices[i].connectionSocket, btLibSocketInfo_SendPending, &sending, sizeof(sending)); if(sending) return true; } return false; } /*********************************************************************** * * FUNCTION: L2CapConnect * * DESCRIPTION: Connect an L2Cap socket * * PARAMETERS: Device index and PSM to connect to * * RETURNED: Error code * ***********************************************************************/ static status_t L2CapConnect(uint8_t index, BtLibL2CapPsmType psm) { status_t error; BtLibSocketConnectInfoType connectInfo; connectInfo.data.L2Cap.remotePsm = psm; connectInfo.data.L2Cap.localMtu = kL2CapSocketMtu; connectInfo.data.L2Cap.minRemoteMtu = kL2CapSocketMtu; if (gDevices[index].connectionSocket != kIOsFdNull) ErrorMsg("Trying to connect already valid socket"); error = BtLibSocketCreate( &gDevices[index].connectionSocket, btLibL2CapProtocol); if( error == btLibErrNoError) { connectInfo.remoteDeviceP = &gDevices[index].bdAddr; error = PbxAddFd( gPollBox, gDevices[index].connectionSocket, POLLIN, HandlePbxSocketEvent, &gDevices[index].connectionSocket ); if(error) return error; error = BtLibSocketConnect(gDevices[index].connectionSocket, &connectInfo); if (error == btLibErrPending) StatusMsg( "Connecting... "); else ErrorMsg("Socket Connect failed"); } else ErrorMsg("L2CAP Socket Creation Failure"); return error; } /*********************************************************************** * * FUNCTION: L2CapFindPsm * * DESCRIPTION: Master uses SDP to find the Whiteboard psm for l2cap * * PARAMETERS: Nothing * * RETURNED: Error code * ***********************************************************************/ static status_t L2CapFindPsm(void) { status_t error = errNone; uint16_t i; for(i = 0; i < gNumDevices; i++) { if(gDevices[i].connectionSocket == kIOsFdNull) { StatusMsg("L2CapFindPsm socket=%d on #%d", gDevices[i].connectionSocket, i); error = BtLibSocketCreate( &gDevices[i].connectionSocket, btLibSdpProtocol); if (!error) { error = PbxAddFd( gPollBox, gDevices[i].connectionSocket, POLLIN, HandlePbxSocketEvent, &gDevices[i].connectionSocket ); if(error) return error; // Use SDP to get the psm, the connection is made in the callback error = BtLibSdpGetPsmByUuid(gDevices[i].connectionSocket, &gDevices[i].bdAddr, (BtLibSdpUuidType*) &kWhiteboardUUID, 1); } if (error == btLibErrPending) StatusMsg("Getting PSM... "); else ErrorMsg("PSM retrieval failed, err 0x%hx", error); break; } } return error; } /*********************************************************************** * * FUNCTION: L2CapDisconnect * * DESCRIPTION: Close socket and disconnect ACL * * PARAMETERS: The socket ref * * RETURNED: Nothing * ***********************************************************************/ static void L2CapDisconnect(BtLibSocketRef socket) { status_t error; uint8_t index; StatusMsg("L2CapDisconnect"); error = BtLibSocketClose(socket); PbxRemoveFd( gPollBox, socket ); index = FindSocketIndex(socket); gDevices[index].connectionSocket = kIOsFdNull; error = BtLibLinkDisconnect(gFdME, &(gDevices[index].bdAddr)); if(gNumConnections == 0) ResetQueue(); } #if 0 #pragma mark - #endif /*********************************************************************** * * FUNCTION: DiscoverAndAdd * * DESCRIPTION: Discover new devices and connect to those selected * * PARAMETERS: nothing * * RETURNED: nothing * ***********************************************************************/ static void DiscoverAndAdd(void) { status_t error; // Boolean showLastDiscovery = false; BtLibClassOfDeviceType deviceFilter = (btLibCOD_ServiceAny | btLibCOD_Major_Computer | btLibCOD_Minor_Comp_Palm); // Discover some devices to add to the piconet, but loop if we exceed the maximum error = BtLibDiscoverDevices(gFdME, NULL, NULL, false, &deviceFilter, 1, false, (BtLibDeviceAddressType *)&(gSelectedDevicesList.selectedDevices), kMaxDevices, &(gSelectedDevicesList.numSelectedDevices)); if(error == btLibErrNoError) { AclCreateLinks(); } } /*********************************************************************** * * FUNCTION: AclCreateLinks * * DESCRIPTION: Create an ACL link to 1 or more devices * * PARAMETERS: nothing * * RETURNED: nothing * ***********************************************************************/ static void AclCreateLinks(void) { gPendingLinks = gSelectedDevicesList.numSelectedDevices; if(gPendingLinks != 0) { StatusMsg("Creating Link(s)"); AclCreateNextLink(); } else StatusMsg("Could not add devices to piconet"); } /*********************************************************************** * * FUNCTION: AclCreateNextLink * * DESCRIPTION: Create the next link based on the gSelectedDevicesList and gPendingLinks * * PARAMETERS: Nothing * * RETURNED: Nothing * ***********************************************************************/ static void AclCreateNextLink(void) { status_t error; // Connect to the next device. In case it fails or it is already done, try with another device do { error = BtLibLinkConnect( gFdME, &(gSelectedDevicesList.selectedDevices[gSelectedDevicesList.numSelectedDevices - gPendingLinks]) ); if(error != btLibErrPending) { gPendingLinks--; BtLibAddrBtdToA(&(gSelectedDevicesList.selectedDevices[gSelectedDevicesList.numSelectedDevices - gPendingLinks]), gBdAddrStr, kBdAddrStrSize); StatusMsg( "ACL to %s failed, continuing", gBdAddrStr); } } while (error != btLibErrPending && gPendingLinks != 0); } /*********************************************************************** * * FUNCTION: SocketHandleDisconnect * * DESCRIPTION: Handle ACL disconnection * * PARAMETERS: Nothing * * RETURNED: Nothing * ***********************************************************************/ static void SocketHandleDisconnect(BtLibDeviceAddressTypePtr bdAddr) { uint8_t i; i = FindDeviceIndex(bdAddr); if (i < gNumDevices) { if (gDevices[i].connectionSocket != kIOsFdNull) { StatusMsg("SocketHandleDisconnect"); gNumConnections--; BtLibSocketClose( gDevices[i].connectionSocket); PbxRemoveFd( gPollBox, gDevices[i].connectionSocket ); } if(i < gNumDevices-1) { // Compact array of device memmove(&gDevices[i], &gDevices[i+1], (gNumDevices - i) * sizeof(BtDeviceInfoType)); } gNumDevices--; gDevices[gNumDevices].connectionSocket = kIOsFdNull; } if(!gIsMaster) FrmGotoForm(gDbResId, MainForm); } /*********************************************************************** * * FUNCTION: DestroyPiconet * * DESCRIPTION: Close all sockets, ACL connections and destroy the piconet * * PARAMETERS: Nothing * * RETURNED: Nothing * ***********************************************************************/ static void DestroyPiconet(void) { uint16_t i; StatusMsg("DestroyPiconet"); for (i=0; i kMaxQueueSize ) { secondSegmentSize = (headIndex + num) % kMaxQueueSize; } firstSegmentSize = num - secondSegmentSize; // Handle case where we might try to send too much if(sizeof(WhiteBoardRemoteDrawEvent) * firstSegmentSize > kL2CapSocketMtu) { // Put the excess in the second segment secondSegmentSize += firstSegmentSize - (kL2CapSocketMtu/sizeof(WhiteBoardRemoteDrawEvent)); // Send as much as possible firstSegmentSize = kL2CapSocketMtu/sizeof(WhiteBoardRemoteDrawEvent); } // Make sure we are connected as either the server or the client if (!L2CapValidChannel()) { ErrorMsg("L2Cap Session not established"); return 0; } if(L2CapSendPending()) return 0; for(i = 0; i < gNumDevices; i++) { error = BtLibSocketSend(gDevices[i].connectionSocket, (uint8_t*) &gRemoteDrawingQueue[headIndex], sizeof(WhiteBoardRemoteDrawEvent) * firstSegmentSize); if ( (error != errNone) && (error != btLibErrPending) ) { // Send failed. Find out what happened. BtLibDeviceAddressType device; status_t error2; error2 = BtLibSocketGetInfo(gDevices[i].connectionSocket, btLibSocketInfo_RemoteDeviceAddress, &device, sizeof(device)); if (!error2) { BtLibAddrBtdToA( &device, gBdAddrStr, kBdAddrStrSize); ErrorMsg( "Send to %s failed: 0x%hx", gBdAddrStr, error); } return 0; } } // Dequeue the DrawEvents we just sent RemoveHeadOfQueue(firstSegmentSize); // Recurse if needed if(secondSegmentSize) return firstSegmentSize + AttemptToSendNext_N_EnquedEvents(secondSegmentSize); return firstSegmentSize; } #if 0 #pragma mark - #endif /*********************************************************************** * * FUNCTION: OffscreenDrawLine * * DESCRIPTION: Draw a line offscreen and then copy the offscreen window * into the screen window * * PARAMETERS: Line start and end. * * RETURNED: Nothing * ***********************************************************************/ static void OffscreenDrawLine(Coord x1, Coord y1, Coord x2, Coord y2, uint16_t newForeColor) { WinHandle oldWinH; IndexedColorType aForeColor; RGBColorType aRGB[8] = { {0, 0, 0, 0}, {0, 255, 0, 0}, {0, 0, 255, 0}, {0, 0, 0, 255}, {0, 255, 255, 0}, {0, 0, 255, 255}, {0, 255, 0, 255}, {0, 128, 128, 128} }; uint16_t newColorIndex = newForeColor; if (newForeColor >= 8) newColorIndex = newForeColor % 8; TraceOutput(TL( kAppWhiteBoardErrorClass, "OffscreenDrawLine: index=%hu", newColorIndex )); if(gOffscreenWinH) { oldWinH = WinSetDrawWindow(gOffscreenWinH); aForeColor = WinSetForeColor (WinRGBToIndex(&aRGB[newColorIndex])); WinDrawLine( x1, y1, x2, y2); WinSetForeColor (aForeColor); WinSetDrawWindow(oldWinH); if(FrmGetActiveFormID() == DrawingForm) WinCopyRectangle(gOffscreenWinH, NULL, &kDrawRect, 0, 0, winPaint); } } /*********************************************************************** * * FUNCTION: ClearDrawArea * * DESCRIPTION: Erase offscreen window and then copy it * into the screen window * * PARAMETERS: Line start and end. * * RETURNED: Nothing * ***********************************************************************/ static void ClearDrawArea(void) { WinHandle oldWinH; if(gOffscreenWinH) { oldWinH = WinSetDrawWindow(gOffscreenWinH); WinEraseWindow(); WinSetDrawWindow(oldWinH); if(FrmGetActiveFormID() == DrawingForm) WinCopyRectangle(gOffscreenWinH, NULL, &kDrawRect, 0, 0, winPaint); } } /*********************************************************************** * * FUNCTION: FindDeviceIndex * * DESCRIPTION: Find a device in the array from its address * and return its index * * PARAMETERS: The BT address * * RETURNED: The index * ***********************************************************************/ static uint8_t FindDeviceIndex(BtLibDeviceAddressTypePtr bdAddrP) { uint8_t i; for(i = 0; i < gNumDevices; i++) { if(memcmp(&gDevices[i].bdAddr, bdAddrP, sizeof(BtLibDeviceAddressType)) == 0) break; } return i; } /*********************************************************************** * * FUNCTION: FindSocketIndex * * DESCRIPTION: Find a device in the array from its socket ref * and return its index * * PARAMETERS: The socket ref * * RETURNED: The index * ***********************************************************************/ static uint8_t FindSocketIndex(BtLibSocketRef socket) { uint8_t i; for(i = 0; i < gNumDevices; i++) { if(gDevices[i].connectionSocket == socket) break; } return i; } /*********************************************************************** * * FUNCTION: ChangeLabel * * DESCRIPTION: Utility function to change a label * Note: The initial label (done in Constructor) MUST be * longer than any label you copy over it!! * * PARAMETERS: labelID - id of the label to change * char* - new label ptr. label is copied & need not persist * * RETURNED: Nothing * ***********************************************************************/ static void ChangeLabel( uint16_t labelID, const char *newLabel) { FormPtr frm = FrmGetActiveForm(); uint16_t labelIndex = FrmGetObjectIndex(frm, labelID); //Hide the label first to erase old text FrmHideObject(frm, labelIndex); //copy in the new string FrmCopyLabel(frm, labelID, newLabel); //now force the redraw FrmShowObject(frm, labelIndex); } /*********************************************************************** * * FUNCTION: UpdateStatusDisplay * * DESCRIPTION: Displays the status message on the screen. This is called during a nilEvent * * PARAMETERS: nothing * * RETURNED: nothing * ***********************************************************************/ static void UpdateStatusDisplay(void) { // Keep the error displayed for a while if (gTimeToClearError != 0) { if (TimGetSeconds() < gTimeToClearError) return; gTimeToClearError = 0; } switch (FrmGetActiveFormID()) { case MainForm: ChangeLabel(MainStatusLabel, gStatus); break; case DrawingForm: ChangeLabel(DrawingStatusLabel, gStatus); break; } } /*********************************************************************** * * FUNCTION: StatusMsg * * DESCRIPTION: Updates the status message string. The screen is only updated occasionally * * PARAMETERS: same as PrintF * * RETURNED: nothing * ***********************************************************************/ static void StatusMsg(const char *formatStr, ...) { _Palm_va_list args; va_start(args, formatStr); StrVPrintF(gStatus, formatStr, args); va_end(args); TraceOutput(TL( kAppWhiteBoardErrorClass, "StatusMsg: '%s'", gStatus )); UpdateStatusDisplay(); } /*********************************************************************** * * FUNCTION: ErrorMsg * * DESCRIPTION: Updates the error message string and display it. * * PARAMETERS: same as PrintF * * RETURNED: nothing * ***********************************************************************/ static void ErrorMsg(const char *formatStr, ...) { _Palm_va_list args; va_start(args, formatStr); StrVPrintF(gStatus, formatStr, args); va_end(args); TraceOutput(TL( kAppWhiteBoardErrorClass, "ErrorMsg: '%s'", gStatus )); UpdateStatusDisplay(); // Display the error for at least 5 seconds gTimeToClearError = TimGetSeconds() + 5; } #if 0 #pragma mark - #endif /*********************************************************************** * * FUNCTION: AppHandleEvent * * DESCRIPTION: This routine loads form resources and set the event * handler for the form loaded. * * PARAMETERS: event - a pointer to an EventType structure * * RETURNED: true if the event has handle and should not be passed * to a higher level handler. * ***********************************************************************/ static Boolean AppHandleEvent(EventPtr eventP) { uint16_t formId; FormPtr frmP; if (eventP->eType == frmLoadEvent) { // Load the form resource. formId = eventP->data.frmLoad.formID; frmP = FrmInitForm(gDbResId, formId); FrmSetActiveForm(frmP); // Set the event handler for the form switch (formId) { case DrawingForm: FrmSetEventHandler(frmP, DrawingFormHandleEvent); break; case MainForm: FrmSetEventHandler(frmP, MainFormHandleEvent); break; } return true; } return false; } /*********************************************************************** * * FUNCTION: HandlePbxSocketEvent * * DESCRIPTION: Handle events from "socket" devices (L2CAP, RfComm, SDP). * ***********************************************************************/ static void HandlePbxSocketEvent( struct PollBox* pbx, // the PollBox struct pollfd* pollFd, // fd, event mask, and received events void* context // in this app, ptr to global fd variable ) { status_t error; int32_t flags; static BtLibSocketEventType sEvent; static char sData[kMyMtu]; static struct strbuf ctlBuf = { sizeof(sEvent), 0, (char*)&sEvent }; static struct strbuf datBuf = { sizeof(sData), 0, (char*)&sData[0] }; uint8_t index = 0; TraceOutput(TL(kAppWhiteBoardErrorClass, "HandlePbxSocketEvent: fd=%lx, *context=%lx", pollFd->fd, (context)?*((int32_t*)context):0 )); // We must be here for a reason ... DbgOnlyFatalErrorIf( !(pollFd->revents & (POLLIN|POLLERR|POLLHUP|POLLNVAL) ), "no event flag" ); // Our context variable is just the global variable containing the file descriptor. // DbgOnlyFatalErrorIf( context != &gFdSdp && context != &gFdListener && context != &gFdData, "bad context" ); DbgOnlyFatalErrorIf( pollFd->fd != *((int32_t*)context), "context not fd" ); DbgOnlyFatalErrorIf( pollFd->fd < 0, "bad fd" ); // Check for error/eof from poll, read the event message. flags = 0; if ( (pollFd->revents & (POLLERR|POLLHUP|POLLNVAL)) || // poll error or hangup IOSGetmsg( pollFd->fd, &ctlBuf, &datBuf, &flags, &error ) != 0 // getmsg error ) { TraceOutput(TL( kAppWhiteBoardErrorClass, "HandlePbxSocketEvent: closing fd %ld", pollFd->fd )); PbxRemoveFd( pbx, pollFd->fd ); IOSClose( pollFd->fd ); *(int32_t*)context = -1; return; } TraceOutput(TL(kAppWhiteBoardErrorClass, "HandlePbxSocketEvent: fd=%lx, ctlBuf.len=%hd, datBuf.len=%hd", pollFd->fd, ctlBuf.len, datBuf.len)); // Check for pure data. if ( ctlBuf.len == -1 ) // if ( datBuf.len != -1 ) { // Process a drawing event locally. The master also forwards it to slaves. WhiteBoardRemoteDrawEvent *drawDataP; uint16_t i,numEvents; TraceOutput(TL(kAppWhiteBoardErrorClass, "HandlePbxSocketEvent: Socket %ld: Data: %s", pollFd->fd, datBuf.buf )); // For each drawing events numEvents = datBuf.len / sizeof(WhiteBoardRemoteDrawEvent); for( i = 0; i < numEvents; i++) { drawDataP = ((WhiteBoardRemoteDrawEvent*) datBuf.buf) + i; // Execute it locally if(drawDataP->clear) ClearDrawArea(); else OffscreenDrawLine( NToHS(drawDataP->lineStart.x), NToHS(drawDataP->lineStart.y), NToHS(drawDataP->lineStop.x), NToHS(drawDataP->lineStop.y), pollFd->fd); // Master also forwards it to slave if(gIsMaster) { AddDrawEventToQueue(drawDataP->clear, NToHS(drawDataP->lineStart.x), NToHS(drawDataP->lineStart.y), NToHS(drawDataP->lineStop.x), NToHS(drawDataP->lineStop.y)); } } if(gIsMaster) AttemptQueueSend(); // send update to screen FrmUpdateForm (FrmGetActiveFormID(), frmRedrawUpdateCode); return; } // Since it's not pure data, we must have an event struct in the control part. DbgOnlyFatalErrorIf( ctlBuf.len != sizeof(BtLibSocketEventType), "no event struct" ); // Decode the event. switch ( sEvent.event ) { case btLibSocketEventSdpGetServerChannelByUuid: if ( sEvent.status != btLibErrNoError ) { TraceOutput(TL( kAppWhiteBoardErrorClass, "HandlePbxSocketEvent: SDP Get Channel error: 0x%lx", sEvent.status )); } else { BtLibSocketConnectInfoType connectInfo; index = FindSocketIndex(pollFd->fd); TraceOutput(TL( kAppWhiteBoardErrorClass, "HandlePbxSocketEvent: Retrieved Channel ID: 0x%ld", (int)sEvent.eventData.sdpByUuid.param.channel )); // Close the SDP socket that was used to make the query. DbgOnlyFatalErrorIf( pollFd->fd != gDevices[index].connectionSocket, "not sdp socket" ); TraceOutput(TL( kAppWhiteBoardErrorClass, "HandlePbxSocketEvent: closing SDP socket %ld", pollFd->fd )); SocketHandleDisconnect(&gDevices[index].bdAddr ); index = FindSocketIndex(pollFd->fd); // Create a data socket. error = BtLibSocketCreate( &gDevices[index].connectionSocket, btLibRfCommProtocol ); if ( error ) { TraceOutput(TL( kAppWhiteBoardErrorClass, "HandlePbxSocketEvent: RFCOMM SocketCreate Error: 0x%lx", error )); } else { gDevices[index].bdAddr = sEvent.eventData.requestingDevice; PbxAddFd( gPollBox, gDevices[index].connectionSocket, POLLIN, HandlePbxSocketEvent, &gDevices[index].connectionSocket ); // Connect the data socket. connectInfo.remoteDeviceP = &sEvent.eventData.requestingDevice; connectInfo.data.RfComm.remoteService = sEvent.eventData.sdpByUuid.param.channel; connectInfo.data.RfComm.maxFrameSize = kMyMtu; connectInfo.data.RfComm.advancedCredit = 0; error = BtLibSocketConnect( gDevices[index].connectionSocket, &connectInfo ); if ( error != btLibErrPending ) { TraceOutput(TL( kAppWhiteBoardErrorClass, "HandlePbxSocketEvent: RFCOMM Connect Error: 0x%lx", error )); } else { TraceOutput(TL( kAppWhiteBoardErrorClass, "HandlePbxSocketEvent: Socket %ld: connecting...", gDevices[index].connectionSocket )); } } } break; case btLibSocketEventSdpGetPsmByUuid: { if(sEvent.status == btLibErrNoError) { StatusMsg("HandlePbxSocketEvent: btLibSocketEventSdpGetPsmByUuid SDP socket %ld", pollFd->fd); index = FindSocketIndex(pollFd->fd); error = BtLibSocketClose(pollFd->fd); PbxRemoveFd(pbx, pollFd->fd); if(error != btLibErrNoError) ErrorMsg("Could not close SDP socket"); gDevices[index].connectionSocket = kIOsFdNull; //Found the psm so lets connect to the device L2CapConnect(index, sEvent.eventData.sdpByUuid.param.psm); } else { // Treat failed search like a disconnect L2CapDisconnect(sEvent.eventData.newSocket); } break; } case btLibSocketEventConnectRequest: // Slave always accept master connection // Allow only one connection at a time DbgOnlyFatalErrorIf( pollFd->fd != gFdListener, "not listener socket" ); error = BtLibSocketRespondToConnection( pollFd->fd, true ); if ((error != errNone) && (error != btLibErrPending)) { TraceOutput(TL( kAppWhiteBoardErrorClass, "HandlePbxSocketEvent: Inbound Req refused Error: 0x%lx", error )); } else { TraceOutput(TL( kAppWhiteBoardErrorClass, "HandlePbxSocketEvent: Socket %ld: Inbound Req Accepted", pollFd->fd )); } break; case btLibSocketEventConnectedOutbound: // Master just connected to a slave index = FindSocketIndex(pollFd->fd); DbgOnlyFatalErrorIf( pollFd->fd != gDevices[index].connectionSocket, "not data socket" ); if ( sEvent.status == btLibErrNoError ) { // Connection succeeded. TraceOutput(TL( kAppWhiteBoardErrorClass, "HandlePbxSocketEvent: Socket %ld: Outbound Conn OK", pollFd->fd )); StatusMsg("Draw"); gNumConnections++; } else { // Connection failed. TraceOutput(TL( kAppWhiteBoardErrorClass, "HandlePbxSocketEvent: Socket %ld: Outbound Conn ERROR 0x%lx", pollFd->fd, sEvent.status )); } break; case btLibSocketEventConnectedInbound: // Slave is connected by master index = FindSocketIndex(sEvent.eventData.newSocket); DbgOnlyFatalErrorIf( pollFd->fd != gFdListener, "not listener socket" ); DbgOnlyFatalErrorIf( gDevices[index].connectionSocket != kIOsFdNull, "already connected" ); BtLibAddrBtdToA(&sEvent.eventData.requestingDevice, gBdAddrStr, kBdAddrStrSize); TraceOutput(TL( kAppWhiteBoardErrorClass, "HandlePbxSocketEvent: in socket=%d New Inbound Conn on #%d, addr %s", sEvent.eventData.newSocket, index, gBdAddrStr )); gDevices[0].connectionSocket = sEvent.eventData.newSocket; PbxAddFd( gPollBox, gDevices[0].connectionSocket, POLLIN, HandlePbxSocketEvent, &gDevices[0].connectionSocket ); gNumConnections++; FrmGotoForm(gDbResId, DrawingForm); StatusMsg("Draw"); break; case btLibSocketEventDisconnected: // Master or slave lost a connection. Close socket and disconnect ACL TraceOutput(TL( kAppWhiteBoardErrorClass, "HandlePbxSocketEvent: Socket=%ld: Disconnect (event=%d)", pollFd->fd, sEvent.eventData.newSocket )); index = FindSocketIndex(pollFd->fd); if ( pollFd->fd == gDevices[index].connectionSocket ) { // An established data connection was broken. // Close the data socket. StatusMsg("HandlePbxSocketEvent: btLibSocketEventDisconnected"); PbxRemoveFd( pbx, gDevices[index].connectionSocket ); IOSClose( gDevices[index].connectionSocket ); gDevices[index].connectionSocket = kIOsFdNull; gNumConnections--; } else if ( pollFd->fd == gFdListener ) { // An inbound connection failed. // Must close both the listener socket and the new socket returned in the event. PbxRemoveFd( pbx, gFdListener ); IOSClose( gFdListener ); IOSClose( sEvent.eventData.newSocket ); gFdListener = kIOsFdNull; gNumConnections--; } else { DbgOnlyFatalError( "wrong socket" ); } break; } } /*********************************************************************** * * FUNCTION: HandlePbxMeEvent * * DESCRIPTION: Handle events from the Management Entity device. * ***********************************************************************/ static void HandlePbxMeEvent( struct PollBox* pbx, // the PollBox struct pollfd* pollFd, // fd, event mask, and received events void* context // not used here ) { status_t error; int32_t flags; static BtLibManagementEventType mEvent; static char mData[sizeof(BtLibFriendlyNameType)]; static struct strbuf ctlBuf = { sizeof(mEvent), 0, (char*)&mEvent }; static struct strbuf datBuf = { sizeof(mData), 0, (char*)&mData[0] }; // We must be here for a reason ... DbgOnlyFatalErrorIf( !(pollFd->revents & (POLLIN|POLLERR|POLLHUP|POLLNVAL) ), "no event flag" ); // We must have the Management Entity file descriptor. DbgOnlyFatalErrorIf( pollFd->fd != gFdME, "not the FdMe" ); DbgOnlyFatalErrorIf( pollFd->fd < 0, "gFdMe closed" ); // Check for error/eof from poll, read the event message. flags = 0; if ( (pollFd->revents & (POLLERR|POLLHUP|POLLNVAL)) || // poll error or hangup IOSGetmsg( pollFd->fd, &ctlBuf, &datBuf, &flags, &error ) != 0 // getmsg error ) { TraceOutput(TL( kAppWhiteBoardErrorClass, "HandlePbxBtEvent: closing fd %ld", pollFd->fd )); PbxRemoveFd( pbx, pollFd->fd ); BtLibClose( pollFd->fd ); return; } // We must have an event struct in the control part. DbgOnlyFatalErrorIf( ctlBuf.len != sizeof(BtLibManagementEventType), "no event struct" ); // Decode the event. switch ( mEvent.event ) { case btLibManagementEventRadioState: { const char* s; switch ( mEvent.status ) { case btLibErrRadioInitialized: s = "Radio Initialized"; break; case btLibErrRadioInitFailed: s = "Radio Init Failed"; break; case btLibErrRadioFatal: s = "Radio Fatal Error"; break; case btLibErrRadioSleepWake: s = "Radio Wakeup"; break; default: s = "Radio Event 0x%lx"; break; } TraceOutput(TL( kAppWhiteBoardErrorClass, s, mEvent.status)); } break; case btLibManagementEventPiconetCreated: break; case btLibManagementEventPiconetDestroyed: FrmGotoForm(gDbResId, MainForm); break; case btLibManagementEventACLConnectOutbound: gPendingLinks--; if (gIsMaster) { // Lock when outbound links are complete if(gPendingLinks == 0) BtLibPiconetLockInbound(gFdME); else AclCreateNextLink(); } else { // We should have connected to a master of a Piconet, check that we are really a slave // (a master/slave switch should have occured) BtLibConnectionRoleEnum role; status_t error; error = BtLibLinkGetState(gFdME, &mEvent.eventData.bdAddr, btLibLinkPref_LinkRole, (void *)&role, sizeof(role)); if (error || role != btLibSlaveRole) { BtLibLinkDisconnect(gFdME, &mEvent.eventData.bdAddr); break; } } // Intentional fall through case btLibManagementEventACLConnectInbound: BtLibAddrBtdToA(&mEvent.eventData.bdAddr, gBdAddrStr, kBdAddrStrSize); if(mEvent.status == btLibErrNoError) { memmove(&gDevices[gNumDevices].bdAddr, &mEvent.eventData.bdAddr, sizeof(BtLibDeviceAddressType)); StatusMsg("Link to %s Created on #%d", gBdAddrStr, gNumDevices); gNumDevices++; } else ErrorMsg("Link to %s Failed", gBdAddrStr); if (gIsMaster && gPendingLinks == 0) L2CapFindPsm(); break; case btLibManagementEventACLDisconnect: BtLibAddrBtdToA(&mEvent.eventData.bdAddr, gBdAddrStr, kBdAddrStrSize); StatusMsg("%s Disc: %hx", gBdAddrStr, mEvent.status); SocketHandleDisconnect(&mEvent.eventData.bdAddr); break; case btLibManagementEventAccessibilityChange: { FormPtr frmP = FrmGetActiveForm(); ControlPtr ctrlP = NULL; if (FrmGetActiveFormID() == DrawingForm) ctrlP = FrmGetObjectPtr(frmP, FrmGetObjectIndex(frmP, DrawingToggleLockButton)); switch( mEvent.eventData.accessible) { case btLibNotAccessible: StatusMsg( "Not Accessible"); if (ctrlP) CtlSetLabel(ctrlP, kUnlockStr); break; case btLibConnectableOnly: StatusMsg( "Connectable"); if (ctrlP) CtlSetLabel(ctrlP, kLockStr); break; case btLibDiscoverableAndConnectable: StatusMsg( "Discoverable and Connectable"); if (ctrlP) CtlSetLabel(ctrlP, kLockStr); break; } } break; } } /*********************************************************************** * * FUNCTION: HandlePbxUIEvent * * DESCRIPTION: Handle UI events. * ***********************************************************************/ static void HandlePbxUIEvent( PollBox* pbx, // the PollBox struct pollfd* pollFd, // fd, event mask, and received events void* context // user context ) { EventType event; status_t error; UNUSED_ARG( pbx ); UNUSED_ARG( pollFd ); UNUSED_ARG( context ); do { EvtGetEvent(&event, SysTicksPerSecond() / 10); if (event.eType == appStopEvent) { gStopFlag = true; } if (! SysHandleEvent(&event)) if (! MenuHandleEvent(0, &event, &error)) if (! AppHandleEvent(&event)) FrmDispatchEvent(&event); } while ( EvtEventAvail()); } //----------------------------------------- // until polling fd 0 works correctly. // #if CAN_POLL_FD_0 #define MyPbxPoll( pbx, nReadyP ) PbxPoll( pbx, -1, nReadyP ) #else static status_t MyPbxPoll( PollBox* pbx, int32_t* nReadyP ) { HandlePbxUIEvent( 0, 0, 0 ); return PbxPoll( pbx, 1, nReadyP ); } #endif /*********************************************************************** * * FUNCTION: AppEventLoop * * DESCRIPTION: This routine is the event loop for the application. * * PARAMETERS: nothing * * RETURNED: nothing * ***********************************************************************/ static void AppEventLoop( void ) { int32_t nReady; status_t err; while ( !gStopFlag ) { err = MyPbxPoll( gPollBox, &nReady ); ErrFatalErrorIf( err, "poll error" ); ErrFatalErrorIf( gPollBox->count == 0, "empty pollbox" ); } } /*********************************************************************** * * FUNCTION: AppStart * * DESCRIPTION: Initialize the application * * PARAMETERS: Nothing * * RETURNED: Err value 0 if nothing went wrong * ***********************************************************************/ static status_t AppStart(void) { status_t err; gSavedAutoOff = SysSetAutoOffTime(0); // Open our resource database. err = SysGetModuleDatabase( SysGetRefNum(), NULL, &gDbResId ); if (!gDbResId) { TraceOutput(TL(kAppWhiteBoardErrorClass, "AppStart: DmOpenDatabaseByTypeCreator error")); } ErrFatalErrorIf( err, "can't get resource db" ); // Create our pollbox, and put file descriptor 0 into it, for UI events. gPollBox = PbxCreate(); #if CAN_POLL_FD_0 gFdUI = EvtGetEventDescriptor(); PbxAddFd( gPollBox, gFdUI, POLLIN, HandlePbxUIEvent, NULL ); #else { uint32_t fd = IOSOpen( "NULL", O_RDWR, &err ); DbgOnlyFatalErrorIf( fd < 0, "can't open NULL dev" ); PbxAddFd( gPollBox, fd, POLLIN, NULL, NULL ); } #endif return BluetoothInit(); } /*********************************************************************** * * FUNCTION: AppStop * * DESCRIPTION: Terminate the application. * * PARAMETERS: Nothing * * RETURNED: Nothing * ***********************************************************************/ static void AppStop(void) { SysSetAutoOffTime(gSavedAutoOff); BluetoothCleanup(); #if CAN_POLL_FD_0 PbxRemoveFd(gPollBox, gFdUI); #endif FrmCloseAllForms(); PbxDestroy( gPollBox ); // note this closes any fds still in the box } /*********************************************************************** * * FUNCTION: PilotMain * * DESCRIPTION: This is the main entry point for the application. * * PARAMETERS: cmd - word value specifying the launch code. * cmdPB - pointer to a structure that is associated with the launch code. * launchFlags - word value providing extra information about the launch. * * RETURNED: Result of launch * ***********************************************************************/ uint32_t PilotMain(uint16_t cmd, MemPtr cmdPBP, uint16_t launchFlags) { status_t error = 0; // uint32_t btVersion; UNUSED_ARG( cmdPBP ); UNUSED_ARG( launchFlags ); /* // Check that Bluetooth components are intalled, which imply Palm OS 4.0 or upper if (FtrGet(btLibFeatureCreator, btLibFeatureVersion, &btVersion) != errNone) { // Alert the user if it's the active application if ((launchFlags & sysAppLaunchFlagNewGlobals) && (launchFlags & sysAppLaunchFlagUIApp)) FrmAlert (gDbResId, MissingBtComponentsAlert); return sysErrRomIncompatible; } */ switch (cmd) { case sysAppLaunchCmdNormalLaunch: error = AppStart(); TraceOutput(TL( kAppWhiteBoardErrorClass, "PilotMain: error= 0x%lX", error )); if (!error) { FrmGotoForm(gDbResId, MainForm); AppEventLoop(); } AppStop(); break; default: break; } return error; }