// Copyright © 1994-1995 Apple Computer, Inc. All rights reserved constant kAppSymbol := '|ACME Modem:PIEDTS|; // Asynchronous Connection Manager Example for Modem endpoints constant kAppName := "ACME Modem"; constant kMaxAppWidth := 240; // original MP width constant kMaxAppHeight := 336; // original MP height constant kRetryMaxCount:= 2; // max number of times MConnect will try again to establish a connection constant kState_Disconnected := 0; // ready-to-go (default state) constant kState_Listen := 1; // preparation for (asynchronous) listen constant kState_Listening := 2; // in-process of (asynchronous) listen constant kState_Connect := 3; // preparation for (asynchronous) connect constant kState_Connecting := 4; // in-process of (asynchronous) connect constant kState_Connected := 5; // connected (requires disconnect) constant kState_Disconnecting := 6; // in-process of (asynchronous) disconnect constant kError_EndpointInUse := -666; constant kMessage_EndpointInUse := "Another application seems to be using the communications port."; constant kMessage_Disconnected := "Ready to connectÉ"; constant kMessage_Listening := "Waiting for connection..."; constant kMessage_Connecting := "Connecting..."; constant kMessage_Connected := "Connected, awaiting disconnect..."; constant kMessage_Disconnecting := "Disconnecting, please wait..."; constant kMessage_Retry := "No response... Will try again..."; constant kMessage_NoPhoneNumber := "Please specify a phone number to dial."; constant kMessage_ConnectFailed := "Connection not established; no response."; constant kMessage_BufferOverrun := "The communications data buffer was overrun and has been reset."; constant kMessage_LostConnection := "The connection seems to have dropped."; constant kMessage_ModemNotResponding := "The modem is not responding."; constant kMessage_ModemPhoneLineBusy := "The line is busy."; constant kMessage_ModemLineNoAnswer := "There was no answer."; constant kMessage_ModemNoDialtone := "There is no dialtone."; // the following constants (kDA_StatusFrame, kDA_ActionFunc, kDA_AddDeferredAction) // are used to implement "killable" deferred actions --> see MAddDeferredAction for more info // DefConst( 'kDA_StatusFrame, { fRunIt: true, fRanIt: nil, RanIt: func() fRanIt, KillIt: func() begin fRunIt := nil; fRanIt; end, } ); DefConst( 'kDA_ActionFunc, func(fn, args, status) begin status.fRanIt := true; if status.fRunIt then Apply(fn, args); end ); DefConst( 'kDA_AddDeferredAction, func(fn, args) begin local status := {_proto: kDA_StatusFrame}; AddDeferredAction(kDA_ActionFunc, [fn, args, status]); status; end ); // ---- End Project Data ---- // ---- File ACME Modem.t ---- // Before Script for "vMainApp" // Copyright © 1994-1995 Apple Computer, Inc. All rights reserved. vMainApp := { MAddDeferredAction: /* use this method like you would use AddDeferredAction the difference is that this routine returns a frame containing closures which can be used to "cancel" the deferred action before it executes and to query whether or not the deferred action has executed Example: local x := :MAddDeferredAction( func() GetRoot():SysBeep(), [] ); if x:RanIt() then print("already executed"); else print("waiting to run"); if x:KillIt() then print("already executed"); else print("canceled"); obviously you'll keep x around in a slot somewhere if you ever want to cancel the deferred action... */ kDA_AddDeferredAction // (fn, args), viewSetupDoneScript: func() begin AddPowerOffHandler(SELF); :MSetEndPointState(fEndPointState); // do NOT change the endpoint state -- just update any views that depend on it if fEndPointState = kState_Disconnected then :MMessage(kMessage_Disconnected); else :MMessage(""); end, MMessage: func(message) // this routine can be called regardless of the value of SELF begin local appBaseView := GetRoot().(kAppSymbol); if call kViewIsOpenFunc with (appBaseView) then begin SetValue(appBaseView.vMessage, 'text, Clone(message)); RefreshViews(); end end, viewFormat: 83951953, viewQuitScript: func() begin fEndPoint.fQuitting := true; :MDisconnect(); RemovePowerOffHandler(SELF); end, MConnect: func() begin if fEndPointState <> kState_Disconnected then return; if not StrFilled(vPhone.text) then begin :MNotify(kMessage_NoPhoneNumber); return; end; :MSetEndPointState(kState_Connect); fEndPoint.fConnectAddress.data.addressData := vPhone.text; fEndPoint.fRetryCount := kRetryMaxCount; fEndPoint.fRetryWhat := 'MConnect; fEndPoint.fFlushLevel := 0; fEndPoint.fDeferredObj := :MAddDeferredAction(MConnectAction, [fEndPoint]); end, MDisconnectCompProc: func() // this routine is called from the MDisconnectAction deferred action begin // SELF will be the endpoint frame if fDisconnectSlip then begin fDisconnectSlip:Close(); fDisconnectSlip := NIL; end; fLastError := 0; :MMessage(kMessage_Disconnected); :MSetEndPointState(kState_Disconnected); if fQuitting and (self = fEndPoint) then fEndPoint := nil; else if fRetryWhat and fRetryCount > 0 then begin local retryCount := fRetryCount; Perform(SELF, fRetryWhat, []); fRetryCount := retryCount - 1; end; end, fEndPointOptions: // this options slot is set immediately after a successful Connect // see MConnectCompProc for usage nil , MDisconnectAction: func(ep, state) // this routine is called as a deferred action from MDisconnect begin if state = kState_Connected then ep:Disconnect(); ep:Dispose(); ep:MDisconnectCompProc(); // set up SELF to be the endpoint frame end, fEndPointConfig: [ { label: kCMSModemID, type: 'service, opCode: opSetRequired }, // use a PCMCIA modem if available, else an external modem connected to the serial port { label: kCMOModemECType, type: 'option, opCode: opSetNegotiate, data: kModemECProtocolNone }, // + (kModemECProtocolMNP or kModemECProtocolExternal but not both) and use kMNPDoAllocate in kCMOMNPAllocate frame { label: kCMOMNPAllocate, type: 'option, opCode: opSetRequired, data: kMNPDontAllocate }, // use kMNPDoAllocate if using kMNPCompressionMNP5 and/or kMNPCompressionV42bis { label: kCMOMNPCompression, type: 'option, opCode: opSetNegotiate, data: kMNPCompressionNone }, // + kMNPCompressionMNP5 + kMNPCompressionV42bis { label: kCMOInputFlowControlParms, type: 'option, opCode: opSetNegotiate, data: { useHardFlowControl: NIL, // NEVER specify hardware flow control with modem endpoints useSoftFlowControl: TRUE, xOnChar: unicodeDC1, xOffChar: unicodeDC3, } }, { label: kCMOOutputFlowControlParms, type: 'option, opCode: opSetNegotiate, data: { useHardFlowControl: NIL, // NEVER specify hardware flow control with modem endpoints useSoftFlowControl: TRUE, xOnChar: unicodeDC1, xOffChar: unicodeDC3, } }, { label: kCMOModemDialing, type: 'option, opCode: opSetRequired, data: { speakerOn: TRUE, // or NIL waitForCarrier: 55, // or time in seconds }, speakerOn: TRUE, // bug work-around (duplicate the data.speakerOn slot here) waitForCarrier: 55, }, // bug work-around (duplicate the data.waitForCarrier slot here) ], viewBounds: {left: 0, top: 2, right: 236, bottom: 326}, MExceptionHandler: func(exceptionFrame) // SELF is the endpoint frame when this routine is called begin if exceptionFrame.data exists and exceptionFrame.data <> -16005 // exception generated by ep:Abort() while connected -- really not an error and exceptionFrame.data <> -16013 then begin // exception generated by ep:Abort() during connect -- really not an error if exceptionFrame.data <> fLastError then begin // this prevents a flood of identical exceptions from beating us to death fLastError := exceptionFrame.data; if exceptionFrame.data = -18003 then begin // I/O buffer overrun AddDeferredAction(func(ep) ep:MResetConnection(), [self]); :MNotifyError(exceptionFrame.data); end; else begin // handle all other (unexptected) exceptions by disconnecting the endpoint AddDeferredAction(func(ep) ep:MDisconnect(), [self]); :MNotifyError(exceptionFrame.data); end; end; end; TRUE; end, MListenAction: func(ep) // this routine is called as a deferred action from MListen begin local err := kError_EndpointInUse; try err := ep:Instantiate(ep, NIL) onexception |evt.ex.msg| do begin ep:MNotifyError(err); ep:MSetEndPointState(kState_Disconnected); return; end; ep:MSetEndPointState(kState_Listening); ep:MMessage(kMessage_Listening); err := ep:Listen(NIL); // this blocks, but lets the MListen thread continue execution ep:MConnectCompProc(err); // set up SELF to be the endpoint frame end, _proto: protoApp, MNotifyError: func(error) begin if error = kError_EndpointInUse then :MNotify(kMessage_EndpointInUse); else if error = -38001 then :MNotify(kMessage_ConnectFailed); else if error = -18003 then :MNotify(kMessage_BufferOverrun); else if error = -16009 then :MNotify(kMessage_LostConnection); else if error = -24000 or error = -24005 or error = -24007 then :MNotify(kMessage_ModemNotResponding); else if error = -24003 then :MNotify(kMessage_ModemPhoneLineBusy); else if error = -24002 or error = -24004 then :MNotify(kMessage_ModemLineNoAnswer); else if error = -24001 then :MNotify(kMessage_ModemNoDialtone); else :MNotify("An unexpected error has occured. Error code =" && NumberStr(error)); end, MResetConnection: func() begin fEndPoint.nextInputSpec := NIL; fEndPoint:SetInputSpec(NIL); fEndPoint:Abort(); AddDelayedAction( func(ep) begin ep:SetInputSpec(ep.fInputHandler); end, [fEndPoint], 2500); end, title: "", powerOffScript: func(what) begin if what = 'okToPowerOff and fEndPointState = kState_Disconnected then TRUE else NIL; end, fEndPointState: kState_Disconnected, MDisconnect: func() // this routine may or may not be called from a deferred action begin // SELF can be any frame that inherits to the app base view frame if not fEndPoint then return; if fEndPoint.fFlushLevel <> 0 then return AddDelayedAction(func(base) base:MDisconnect(), [self], 500); if fEndPoint.fDeferredObj and not fEndPoint.fDeferredObj:KillIt() then :MSetEndPointState(kState_Disconnected); if fEndPointState <> kState_Connected and fEndPointState <> kState_Connecting and fEndPointState <> kState_Listening then return; local currentState := fEndPointState; :MSetEndPointState(kState_Disconnecting); :MMessage(kMessage_Disconnecting); fEndPoint.fDisconnectSlip := BuildContext(pt_protoDisconnectSlip); if fEndPoint.fDisconnectSlip then fEndPoint.fDisconnectSlip:Open(); fEndPoint.fRetryCount := 0; fEndPoint.nextInputSpec := NIL; fEndPoint:SetInputSpec(NIL); fEndPoint:Abort(); AddDelayedAction(MDisconnectAction, [fEndPoint, currentState], 2500); end, fEndPoint: NIL, fEndPointInputSpec: { inputForm: 'string, endCharacter: unicodeCR, discardAfter: 256, inputScript: func(ep, data) begin ep:MInput(data); // be sure to set up SELF as the endpoint frame for MInput() end, }, viewSetupFormScript: func() begin // make view no bigger than the original MP local b := GetAppParams() ; viewBounds := RelBounds( b.appAreaLeft, b.appAreaTop, MIN( b.appAreaWidth, kMaxAppWidth ), MIN( b.appAreaHeight, kMaxAppHeight )); SELF.fEndPoint := { _proto: protoEndPoint, _parent: SELF, exceptionHandler: MExceptionHandler, fInputHandler: fEndPointInputSpec, configOptions: fEndPointConfig, fConnectOptions: fEndPointOptions, fConnectAddress: DeepClone(fEndPointAddress), fDisconnectSlip: NIL, fLastError: 0, fRetryCount: 0, fRetryWhat: NIL, fDeferredObj: NIL, fQuitting: NIL, fFlushLevel: 0, // see :MOutput() for more info... }; end, MListen: func() begin if fEndPointState <> kState_Disconnected then return; :MSetEndPointState(kState_Listen); fEndPoint.fRetryCount := kRetryMaxCount; fEndPoint.fRetryWhat := 'MListen; fEndPoint.fDeferredObj := :MAddDeferredAction(MListenAction, [fEndPoint]); end, MOutput: func(data) // SELF can be any frame that inherits to the base app view begin if fEndPointState <> kState_Connected then begin :MNotify("Not connected."); return; end; fEndPoint:Output(data & unicodeCR, NIL); fEndPoint.fFlushLevel := fEndPoint.fFlushLevel + 1; // set this flag to indicate we're entering a kind of "nested event loop" fEndPoint:FlushOutput(); // an unfortunate side-effect of this is that inputSpecs may trigger (YIKES!) fEndPoint.fFlushLevel := fEndPoint.fFlushLevel - 1; // clear the flag end, MSetEndpointState: func(newState) // this routine can be called regardless of the value of SELF begin local appBaseView := GetRoot().(kAppSymbol); // NOTE: We must be absolutely certain fEndPointState gets created/overridden in the app base view frame! appBaseView.fEndPointState := newState; if not call kViewIsOpenFunc with (appBaseView) then return; if appBaseView.fEndPointState = kState_Disconnected then begin appBaseView.vConnect:Show(); appBaseView.vListen:Show(); appBaseView:MSetButtons("Connect", "Listen"); end; else if appBaseView.fEndPointState = kState_Listen then begin appBaseView.vConnect:Hide(); appBaseView:MSetButtons("Listening", NIL); end; else if appBaseView.fEndPointState = kState_Listening then appBaseView:MSetButtons("Stop Listening", NIL); else if appBaseView.fEndPointState = kState_Connect then begin appBaseView.vListen:Hide(); appBaseView:MSetButtons("Connecting", NIL); end; else if appBaseView.fEndPointState = kState_Connecting then appBaseView:MSetButtons("Stop Connecting", NIL); else if appBaseView.fEndPointState = kState_Connected then appBaseView:MSetButtons("Disconnect", NIL); else if appBaseView.fEndPointState = kState_Disconnecting then appBaseView:MSetButtons("Disconnecting", NIL); else appBaseView:MSetButtons("I Am Confused", NIL); RefreshViews(); end, fEndPointAddress: { label: kCMARouteLabel, type: 'address, opCode: opSetRequired, data: { addressType: kPhoneNumber, addressData: "" }, }, MInput: func(data) // SELF is the endpoint frame begin if fFlushLevel = 0 then // see :MOutput() for more info... PlaySound(ROM_SimpleBeep); // inputSpec triggered normally else PlaySound(ROM_PlinkBeep); // inputSpec triggered as side-effect of a FlushOutput call :MMessage(data); // :SetInputSpec(fInputHandler); // it is only necessary to set the inputSpec again if the inputSpec has changed since the last trigger end, MConnectCompProc: func(err) // this routine is called from the MConnectAction deferred action begin // SELF is the endpoint frame if not err then begin fRetryCount := 0; if fConnectOptions then :SetOptions(fConnectOptions); :MSetEndPointState(kState_Connected); :MMessage(kMessage_Connected); :SetInputSpec(fInputHandler); end; else if err <> -10039 then begin // -10039 means :Abort() was called to kill an in-progress (asynchronous) :Connect() // so do NOT call :MDisconnect() when this error occurs(!) Just ignore it... local retryCount := fRetryCount; :MDisconnect(); if retryCount > 0 then begin fRetryCount := retryCount; GetRoot():SysBeep(); :MMessage(kMessage_Retry); end; else :MNotifyError(err); end; end, MConnectAction: func(ep) // this routine is called as a deferred action from MConnect begin local err := kError_EndpointInUse; try err := ep:Instantiate(ep, NIL) onexception |evt.ex.msg| do begin ep:MNotifyError(err); ep:MSetEndPointState(kState_Disconnected); return; end; ep:MSetEndPointState(kState_Connecting); ep:MMessage(kMessage_Connecting); err := ep:Connect(ep.fConnectAddress, NIL); // this blocks, but lets the MConnect thread continue execution ep:MConnectCompProc(err); // set up SELF to be the endpoint frame end, debug: "vMainApp", MNotify: func(message) begin GetRoot():Notify(kNotifyAlert, EnsureInternal(kAppName), EnsureInternal(message)); end, MSetButtons: func(connectText, listenText) begin SetValue(vConnect, 'text, connectText); if listenText then SetValue(vListen, 'text, listenText); else SetValue(vListen, 'text, connectText); end }; _view000 := /* child of vMainApp */ {text: "Messages & Received Data:", viewBounds: {left: 8, top: 16, right: 224, bottom: 32}, _proto: protoStaticText }; vMessage := /* child of vMainApp */ {text: "", viewBounds: {left: 9, top: 33, right: 223, bottom: 119}, viewJustify: 0, viewFormat: 337, viewFont: simpleFont18, _proto: protoStaticText, debug: "vMessage" }; // View vMessage is declared to vMainApp _view001 := /* child of vMainApp */ {text: "Message To Send:", viewBounds: {left: 8, top: 128, right: 168, bottom: 144}, _proto: protoStaticText }; vInputArea := /* child of vMainApp */ {viewFlags: 64001, viewFormat: 12625, viewlinespacing: 20, viewFont: 18434, viewBounds: {left: 9, top: 145, right: 167, bottom: 215}, text: "This is a test!", viewclass: 81, debug: "vInputArea" }; // View vInputArea is declared to vMainApp vConnect := /* child of vMainApp */ {text: "", buttonClickScript: func() begin if fEndPointState = kState_Disconnected then :MConnect(); else :MDisconnect(); end, viewBounds: {left: 122, top: 242, right: 222, bottom: 262}, _proto: protoTextButton, debug: "vConnect" }; // View vConnect is declared to vMainApp vListen := /* child of vMainApp */ { buttonClickScript: func() begin if fEndPointState = kState_Disconnected then :MListen(); else :MDisconnect(); end, text: "", viewBounds: {left: 122, top: 270, right: 222, bottom: 290}, _proto: protoTextButton, debug: "vListen" }; // View vListen is declared to vMainApp vSend := /* child of vMainApp */ {text: "Send", buttonClickScript: func() begin :MOutput(if vInputArea.text then vInputArea.text else ""); end, viewBounds: {left: 178, top: 146, right: 222, bottom: 166}, _proto: protoTextButton, debug: "vSend" }; // View vSend is declared to vMainApp _view002 := /* child of vMainApp */ {text: "Phone Number:", viewBounds: {left: 8, top: 224, right: 112, bottom: 240}, _proto: protoStaticText }; vPhone := /* child of vMainApp */ {viewFlags: 305665, viewFormat: 12625, viewlinespacing: 20, viewFont: 18434, viewBounds: {top: 241, left: 9, right: 111, bottom: 291}, text: "", viewSetupFormScript: func() begin text := Clone(userConfiguration.mailPhone); end, viewclass: 81, debug: "vPhone" }; // View vPhone is declared to vMainApp // After Script for "vMainApp" thisView := vMainApp; thisView.title := kAppName; // ---- Beginning of non-used User Protos ---- // ---- File protoDisconnectSlip ---- // Before Script for "_userproto000" // Copyright © 1994-1995 Apple Computer, Inc. All rights reserved. _userproto000 := {viewBounds: {left: 0, top: 0, right: 108, bottom: 44}, viewJustify: 80, powerOffScript: func(what) begin NIL; end, viewSetupDoneScript: func() begin AddPowerOffHandler(SELF); end, viewQuitScript: func() begin RemovePowerOffHandler(SELF); end, _proto: protoFloater }; _view003 := /* child of _userproto000 */ {text: "Disconnecting...\nPlease Wait...", viewBounds: {left: 8, top: 8, right: 104, bottom: 40}, viewJustify: 2, _proto: protoStaticText }; // ---- End of non-used User Protos ---- // ---- Beginning of section for non used Layout files ---- // End of output