// Text of project LlamaTalk written on 11/22/95 at 8:07 PM // Beginning of text file Project Data /* ** Newton Developer Technical Support Sample Code ** ** LlamaTalk, The safest way to use ADSP on the 1.x Newton OS ** Now for use with Newton OS 2.x ** ** by Jim Schram, Newton Developer Technical Support ** ** Copyright © 1994-1995 by Apple Computer, Inc. All rights reserved. ** ** You may incorporate this sample code into your applications without ** restriction. This sample code has been provided "AS IS" and the ** responsibility for its operation is 100% yours. You are not ** permitted to modify and redistribute the source as "DTS Sample Code." ** If you are going to re-distribute the source, we require that you ** make it clear in the source that the code was descended from ** Apple-provided sample code, but that you've made changes. */ constant kMaxAppWidth := 240; // original MP width constant kMaxAppHeight := 336; // original MP height // End of text file Project Data // Beginning of file protoLlamaTalk // Before Script for "LlamaTalk" /* protoLlamaTalk This layout file is copyright © 1994-1995 Apple Computer, Inc. All rights reserved. Modification Status YY/MM/DD Name Comments 95/09/12 Jim Schram Released as 2.0a1 - Now uses protoBasicEndpoint in Newton OS 2.0 95/05/09 Jim Schram Released as 1.0a8 - Modifications to support NTK 1.5 94/09/22 Jim Schram Released as 1.0a7 - removed power-off patch code (install "MP110 Power Off Update.pkg" instead) 94/07/07 Jim Schram Released as 1.0a6 - modified to use kViewIsOpenFunc & kLlamaTalkStuffCStringFunc 94/06/21 Jim Schram Released as 1.0a5 - added improved CloseAppleTalk support 94/06/10 Jim Schram Released as 1.0a4 - added dynamic GotoSleep patch code 94/05/13 Jim Schram Released as 1.0a3 - added support for typed transaction data 94/05/04 Jim Schram Released as 1.0a1 94/03/17 Jim Schram Initial Development --------------------------------------------------------------------- The LlamaTalk packet format is as follows: 1 byte packet type 3 bytes packet length (MSB -> LSB order) N bytes packet data Currently only packet type cLTPacketType_Simple (zero) is implemented. Future packet types will include features such as compression and encryption. All other packet types are reserved. */ DefConst('kDebugLlamaTalk, nil); // true = print comms debugging statements on bunwarmers, nil = supress them DeclareGlobalFn('LT_PRINT, 1); // if kDebugLlamaTalk is true, then a global function LT_PRINT will be installed at run time DefConst('kLT_PRINT, func(data) begin Print(data); // Sleep(30); end ); DefConst('kLlamaTalkState_Disconnected, nil); // ready-to-go (default state) DefConst('kLlamaTalkState_Connect, 1); // preparation for (asynchronous) connect DefConst('kLlamaTalkState_Connecting, 2); // in-process of (asynchronous) connect DefConst('kLlamaTalkState_Connected, 3); // connected (requires disconnect) DefConst('kLlamaTalkState_Disconnecting, 4); // in-process of (asynchronous) disconnect DefConst('kLlamaTalkState_Listen, 5); // preparation for (asynchronous) listen DefConst('kLlamaTalkState_Listening, 6); // in-process of (asynchronous) listen DefConst('kLlamaTalkAction_Connect, 'connect); // specifies active connection DefConst('kLlamaTalkAction_Listen, 'listen); // specifies passive connection DefConst('kLlamaTalkType_nil, 0); // transaction data item types, as used in :MWriteObject() -- DO NOT RENUMBER! DefConst('kLlamaTalkType_true, 1); DefConst('kLlamaTalkType_char, 2); DefConst('kLlamaTalkType_integer, 3); DefConst('kLlamaTalkType_real, 4); DefConst('kLlamaTalkType_string, 5); DefConst('kLlamaTalkType_binary, 6); DefConst('kLlamaTalkError_IllegalOperation, -666); DefConst('kLlamaTalkError_EndpointInUse, -667); DefConst('kLlamaTalkMessage_EndpointInUse, "Another application seems to be using the communications port."); DefConst('kLlamaTalkMessage_BindFailed, "Could not bind the communications tool."); DefConst('kLlamaTalkMessage_Unknown, "An unexpected error has occured."); DefConst('kLlamaTalkQueueTemplate, // create a new queue like this: kLlamaTalkQueueTemplate:MInstantiate('FIFO); { MInstantiate: func(queueType) // currently 'FIFO (first-in-first-out) and 'FILO (first-in-last-out) queues are supported begin if queueType = 'FIFO or queueType = 'FILO then return { _proto: self, fType: queueType, // 'FIFO or 'FILO fNbElem: 0, // 0 - N fData: [], } // array [] of fNbElem enqueued items else return nil; end, MDispose: func() begin fType := nil; fNbElem := nil; fData := nil; return nil; end, MReset: func() // empties the queue, allowing potential garbage collection to occur begin if fType then begin fNbElem := 0; SetLength(fData,0); end; return nil; end, MEnQueue: func(data) // adds a non-nil data item to a queue according to queue type begin if data then if fType = 'FIFO or fType = 'FILO then begin fNbElem := fNbElem + 1; SetLength(fData, fNbElem); fData[fNbElem - 1] := data; return nil; end; return data; end, MDeQueue: func() // removes a data item from a queue according to queue type begin if not fType or fNbElem <= 0 then return nil; if fType = 'FIFO then begin fNbElem := fNbElem - 1; local data := fData[0]; ArrayMunger(fData, 0, 1, nil, 0, 1); end else if fType = 'FILO then begin fNbElem := fNbElem - 1; local data := fData[fNbElem]; ArrayMunger(fData, fNbElem, 1, nil, fNbElem, 1); end else local data := nil; return data; end, MGetQueueSize: func() // return the number of data items in the queue begin if fType = 'FIFO or fType = 'FILO then return fNbElem else return 0; end, MGetDataSize: func() // return the sum of the binary Length() of each data item in the queue begin local len := 0; if fType = 'FIFO or fType = 'FILO then foreach item in fData do len := len + Length(item); return len; end, }); DefConst('kLlamaTalkStreamTemplate, // create a new stream like this: kLlamaTalkStreamTemplate:MInstantiate(); { MInstantiate: func() { _proto: self, fObj: GetDefaultStore():NewVBO('binary, 0), fPos: 0, }, MDispose: func() begin fObj := nil; fPos := -1; end, MOpen: func() nil, MClose: func() SetLength(fObj, fPos), MRequest: func(size) if Length(fObj) < fPos + size then SetLength(fObj, Length(fObj) + Max(size, 256)), MForward: func(size) begin if fPos + size >= Length(fObj) then :MRequest(size); fPos := fPos + size; end, }); LlamaTalk := { MWriteObject: func(data) // WARNING: This func contains work-arounds and currently undocumented functions. Use at your own risk! begin if fLlamaTalkState <> kLlamaTalkState_Connected then return; IF kDebugLlamaTalk THEN LT_PRINT("Enqueueing data..."); local stream := fWriteStream; // cache the stream object locally for efficiency if not data then begin // Note: if-then-else tree is structured for common-case efficiency stream:MRequest(1); StuffByte(stream.fObj, stream.fPos, kLlamaTalkType_nil); // object type = nil stream:MForward(1); end else if IsInstance(data, 'string) then begin local len := StrLen(data); stream:MRequest(len + 4); StuffByte(stream.fObj, stream.fPos, kLlamaTalkType_string); // object type = string StuffWord(stream.fObj, stream.fPos + 1, len); // MSB -> LSB of 2-byte length // StuffByte(stream.fObj, stream.fPos + 1, len >> 8); // MSB of length // StuffByte(stream.fObj, stream.fPos + 2, len); // LSB of length StuffCString(stream.fObj, stream.fPos + 3, data); // N single byte chars (Mac encoding for chars 128-255) stream:MForward(len + 3); // get rid of that zero terminator byte from StuffCString end else if IsInstance(data, 'int) then begin stream:MRequest(5); StuffByte(stream.fObj, stream.fPos, kLlamaTalkType_integer); // object type = integer StuffLong(stream.fObj, stream.fPos + 1, data); // MSB -> LSB of 4 byte sign-extended value stream:MForward(5); end else if IsInstance(data, 'char) then begin stream:MRequest(2); StuffByte(stream.fObj, stream.fPos, kLlamaTalkType_char); // object type = char StuffChar(stream.fObj, stream.fPos + 1, data); // 1 single byte char (Mac encoding for chars 128-255) stream:MForward(2); end else if IsInstance(data, 'boolean) then begin stream:MRequest(1); StuffByte(stream.fObj, stream.fPos, if data then kLlamaTalkType_true // object type = true else kLlamaTalkType_nil); // object type = nil (= false) stream:MForward(1); end else if IsInstance(data, 'real) then begin local s := NumberStr(data); local len := StrLen(s); stream:MRequest(len + 2); StuffByte(stream.fObj, stream.fPos, kLlamaTalkType_real); // object type = real (as an N-char string) StuffPString(stream.fObj, stream.fPos + 1, s); // pascal string representation (length byte followed by chars) stream:MForward(len + 2); end else if IsInstance(data, 'binary) then begin local len := Length(data); stream:MRequest(len + 5); StuffByte(stream.fObj, stream.fPos, kLlamaTalkType_binary); // object type = binary (bytes) StuffLong(stream.fObj, stream.fPos + 1, len); // MSB -> LSB of 4 byte sign-extended length of object BinaryMunger(stream.fObj, stream.fPos + 5, len, data, 0, len); // data bytes stream:MForward(len + 5); end else begin IF kDebugLlamaTalk THEN LT_PRINT("Unsupported data class!!! Cannot enqueue..."); :?MLlamaTalk_StdError("Unsupported data class (" & SPrintObject(ClassOf(data)) & "). Cannot enqueue.", -48215); return nil; end; stream; end, MIsVisible: func() begin if fBusyShapes then true else nil; end, MRead: func() begin if fInputQueue then fInputQueue:MDeQueue() else nil; end, MIsOpen: func() // this function will only work when 'self' is the protoLlamaTalk view! begin // In other words, we're called using: if view:MIsOpen() then... return call kViewIsOpenFunc with (self); end, viewFormat: nil, MBeginTransaction: func() begin if fLlamaTalkState <> kLlamaTalkState_Connected then return; IF kDebugLlamaTalk THEN LT_PRINT("Beginning write transaction..."); fWriteStream := :MNewStream(); fWriteStream:MOpen(); return true; end, viewQuitScript: func() begin :MDisconnect(); fInputQueue := fInputQueue:MDispose(); fOutputQueue := fOutputQueue:MDispose(); fEndPoint := nil; fEndPointAddress := nil; fEndPointConfig := nil; fOutputData := nil; fIsQuietDisconnect := nil; fBusyShapes := nil; // if kDebugLlamaTalk then // RemoveSlot(functions, 'LT_PRINT); end, MGetAddress: func() begin if HasSlot(fEndPointAddress, 'data) and HasSlot(fEndPointAddress.data, 'addressData) then return fEndPointAddress.data.addressData else return fEndPointAddress; end, MNewBinary: func(size) begin // SetLength(SetClass(Clone(""), 'binary), size); GetDefaultStore():NewVBO('binary, size); end, viewDrawScript: func() begin if fLlamaTalkState = kLlamaTalkState_Connected then :MDrawBusyIcon('IDLE) else :MDrawBusyIcon('DISCONNECTED); end, FInputHeaderComponent: { form: 'bytes, termination: { byteCount: 4, }, inputScript: func(ep, data, terminator, options) begin local size := (data[1] << 16) + (data[2] << 8) + data[3]; IF kDebugLlamaTalk THEN LT_PRINT("FInputHeaderComponent called, byte count =" && NumberStr(size)); if data[0] = 0 then begin ep.FInputDataComponent.fDataBuffer := ep:MNewBinary(size); ep.FInputDataComponent.fDataOffset := 0; ep.FInputDataComponent.fDataBytesToGo := size; ep.FInputDataComponent.termination.byteCount := if size < 512 then size else 512; ep:SetInputSpec(ep.FInputDataComponent); end; else begin ep.FInputDataComponent.fDataBytesToGo := size; ep.FInputDataComponent.termination.byteCount := if size < 512 then size else 512; ep:SetInputSpec(ep.FInputDataSkipper); end; end, }, MConnect: func() begin IF kDebugLlamaTalk THEN LT_PRINT("LlamaTalk.MConnect"); if not :MIsOpen() then // return an error if this view has not yet been opened! return kLlamaTalkError_IllegalOperation; if fLlamaTalkState <> kLlamaTalkState_Disconnected then return kLlamaTalkError_IllegalOperation; :MSetLlamaTalkState(kLlamaTalkState_Connect); fEndPoint:MConnectAction(nil, nil); end, fBusyShapes: nil // holds an array of shapes to be drawn during I/O when this view is visible, nil otherwise , MDisconnectCompProc: func(options, result) // SELF is the endpoint frame begin IF kDebugLlamaTalk THEN LT_PRINT("LlamaTalk.MDisconnectCompProc"); try :UnBind(nil) onexception |evt.ex.comm| do nil; try :Dispose() onexception |evt.ex.comm| do nil; if fDisconnectSlip then begin fDisconnectSlip:Close(); fDisconnectSlip := nil; end; :MSetLlamaTalkState(kLlamaTalkState_Disconnected); if fPowerOffState then begin fPowerOffState := nil; PowerOffResume(kAppSymbol); end; UnRegPowerOff(kAppSymbol); end, viewFlags: 32, fEndPointConfigADSP: [ { label: kCMSAppleTalkID, type: 'service, opCode: opSetRequired, }, { label: kCMSAppleTalkID, type: 'option, opCode: opSetRequired, data: { arglist: [ "adsp", ], typelist: ['struct, ['array, 'char, 4], ], }, }, { label: kCMOEndpointName, type: 'option, opCode: opSetRequired, data: { arglist: [ kADSPEndpoint, ], typeList: ['struct, [11, 13, 0], ], }, }, { label: kCMOAppleTalkBuffer, type: 'option, opCode: opSetRequired, data: { arglist: [ kSndBuffer, 511, ], typeList: ['struct, 'ulong, 'ulong, ], }, }, { label: kCMOAppleTalkBuffer, type: 'option, opCode: opSetRequired, data: { arglist: [ kRcvBuffer, 511, ], typeList: ['struct, 'ulong, 'ulong, ], }, }, { label: kCMOAppleTalkBuffer, type: 'option, opCode: opSetRequired, data: { arglist: [ kAtnBuffer, 0, ], typeList: ['struct, 'ulong, 'ulong, ], }, }, ], MDisconnectAction: func(fromState) // SELF is the endpoint frame begin IF kDebugLlamaTalk THEN LT_PRINT("LlamaTalk.MDisconnectAction"); try :Cancel(nil) onexception |evt.ex.comm| do nil; if fromState = kLlamaTalkState_Connected then try :Disconnect(nil, { async: true, // reqTimeout: 3600, completionScript: func(ep, options, result) ep:MDisconnectCompProc(options, result), }) onexception |evt.ex.comm| do :MDisconnectCompProc(nil, CurrentException().error); else :MDisconnectCompProc(nil, nil); end, MIsConnected: func() begin return fLlamaTalkState = kLlamaTalkState_Connected; end, MConnectAsync: func() begin if not :MIsOpen() then // return an error if this view has not yet been opened! return kLlamaTalkError_IllegalOperation; if fLlamaTalkState <> kLlamaTalkState_Disconnected then return kLlamaTalkError_IllegalOperation; :MSetLlamaTalkState(kLlamaTalkState_Connect); fEndPoint:MConnectAction(nil, true); end, viewIdleScript: func() try begin if fLlamaTalkState <> kLlamaTalkState_Connected then begin // if not connected, turn off idle handler :Dirty(); return nil; end; if :MGetState() < 3 then begin // if connection has dropped, disconnect and turn off idle handler :MDisconnect(); if not fIsQuietDisconnect then // avoid calling the error method if we're supposed to keep quiet about it :?MLlamaTalk_StdError("The connection has closed unexpectedly.", -1); return nil; end; if fInputQueue:MGetQueueSize() > 0 then begin // if there is any input data available, call input script if defined :DoDrawing('MDrawBusyIcon, ['INPUT]); :?MLlamaTalk_InputScript(:MRead()); end; if fOutputQueue:MGetQueueSize() > 0 then begin :DoDrawing('MDrawBusyIcon, ['OUTPUT]); fOutputData := fOutputQueue:MDeQueue(); // fOutputData = the transaction to be output (a stream object) local len := Length(fOutputData.fObj); // len = total bytes of this transaction IF kDebugLlamaTalk THEN LT_PRINT("Total length to be written = " & NumberStr(len)); fEndPoint:Output([0, len >> 16, len >> 8, len], nil, nil); // output the transaction length fEndPoint:Output(fOutputData.fObj, nil, nil); // output the transaction data end; if fInputQueue:MGetQueueSize() = 0 and fOutputQueue:MGetQueueSize() = 0 then begin // if no more data to process then slow down the idle handler :DoDrawing('MDrawBusyIcon, ['IDLE]); return 15000 // check for dropped connections every 15 seconds end else return 0; // number of milliseconds to delay until next idle end onexception |evt.ex| do 0;, MIsQuietDisconnect: func() begin return fIsQuietDisconnect; end, MProtoClone: func(obj) begin if not IsFrame(obj) or IsFunction(obj) then Throw('|evt.ex.msg|, "ProtoClone only works with frames."); local new := {_proto: obj}; foreach slot, value in obj do if IsFrame(value) and not IsFunction(value) then new.(slot) := :MProtoClone(value); new; end, MSetProtocol: func(protocol) // returns the endpoint configuration frame, or nil if protocol symbol is unsupported or view is not open begin if not :MIsOpen() then return nil; if IsFrame(protocol) then fEndPointConfig := EnsureInternal(protocol.configOptions) else if IsArray(protocol) then fEndPointConfig := EnsureInternal(protocol) else if protocol = 'MNP then fEndPointConfig := fEndPointConfigMNP else if protocol = 'ADSP then fEndPointConfig := fEndPointConfigADSP else return nil; fEndPointConfig; end, fEndPointConfig: nil // contains the "live" endpoint config frame used during endpoint instantiation , viewBounds: {left: 100, top: 100, right: 116, bottom: 116}, MCountPendingOutputTransactions: func() begin return if fOutputQueue then fOutputQueue:MGetQueueSize() else 0; end, MEndTransaction: func() begin if fLlamaTalkState <> kLlamaTalkState_Connected then return; IF kDebugLlamaTalk THEN LT_PRINT("Transferring transaction to output queue. Enabling idle handler."); fWriteStream:MClose(); fOutputQueue:MEnQueue(fWriteStream); fWriteStream := nil; :SetUpIdle(100); return nil; end, MDrawBusyIcon: func(state) begin if fBusyShapes then begin :DrawShape(fBusyShapes[0].fShape, fBusyShapes[0].fStyle); // erase the background, then draw the appropriate icon if state = 'DISCONNECTED then :DrawShape(fBusyShapes[1].fShape, fBusyShapes[1].fStyle) else if state = 'IDLE then :DrawShape(fBusyShapes[2].fShape, fBusyShapes[2].fStyle) else if state = 'INPUT then :DrawShape(fBusyShapes[3].fShape, fBusyShapes[3].fStyle) else if state = 'OUTPUT then :DrawShape(fBusyShapes[4].fShape, fBusyShapes[4].fStyle) else :DrawShape(fBusyShapes[1].fShape, fBusyShapes[1].fStyle) end; end, MCountPendingInputTransactions: func() begin return if fInputQueue then fInputQueue:MGetQueueSize() else 0; end, fDisconnectSlipTemplate: { _proto: protoFloater, viewBounds: { left: 0, top: 0, right: 108, bottom: 44, }, viewJustify: vjParentCenterH + vjParentCenterV, // 80 stepChildren: [ { _proto: protoStaticText, viewBounds: { left: 8, top: 8, right: 104, bottom: 40, }, viewJustify: vjCenterH, // 2 text: "Disconnecting...\nPlease Wait...", }, ], }, MResetQueues: func() begin fInputQueue:MReset(); fOutputQueue:MReset(); end, MNewStream: func() begin kLlamaTalkStreamTemplate:MInstantiate(); end, fEndPointConfigMNP: [ { label: kCMSMNPID, type: 'service, result: nil, opCode: opSetRequired }, { label: kCMOMNPDataRate, type: 'option, result: nil, opCode: opSetRequired, form: 'template, data: { arglist: [ k38400bps, ], typelist: ['struct, 'ulong, ], }, }, { label: kCMOSerialIOParms, type: 'option, result: nil, opCode: opSetRequired, form: 'template, data: { arglist: [ k1StopBits, kNoParity, k8DataBits, k38400bps, ], typelist: ['struct, 'long, 'long, 'long, 'long, ], }, }, { label: kCMOMNPCompression, type: 'option, result: nil, opCode: opSetNegotiate, form: 'template, data: { arglist: [ kMNPCompressionNone, ], // also works with kMNPCompressionV42bis (but that uses lots more memory) typelist: ['struct, 'ulong, ], }, }, /* { label: kCMOSerialHWChipLoc, type: 'option, opCode: opSetNegotiate, result: nil, form: 'template, data: { arglist:[ kHWLocPCMCIASlot1, 0, ], typelist:['struct, 'ulong, 'ulong, ], }, }, */ ], MSetAddress: func(address) // returns the address, or nil if view is not open begin if :MIsOpen() then if address then if IsFrame(address) then fEndPointAddress := EnsureInternal(address); else fEndPointAddress := MakeAppleTalkOption(address); else fEndPointAddress := nil; else nil; end, MNewQueue: func(queueType) begin kLlamaTalkQueueTemplate:MInstantiate(queueType); end, MDisconnect: func() // NOTE: this method may also be called from :MEndPointExceptionHandler in which case SELF is the endpoint frame begin IF kDebugLlamaTalk THEN LT_PRINT("LlamaTalk.MDisconnect"); if fLlamaTalkState <> kLlamaTalkState_Connected and fLlamaTalkState <> kLlamaTalkState_Connecting and fLlamaTalkState <> kLlamaTalkState_Listening then return; :MSetQuietDisconnect(true); // supress user alerts and other interactions while disconnecting IF kDebugLlamaTalk THEN LT_PRINT("LlamaTalk.MDisconnect -- Disconnecting endpoint"); local fromState := fLlamaTalkState; :MSetLlamaTalkState(kLlamaTalkState_Disconnecting); fEndPoint.fDisconnectSlip := BuildContext(fDisconnectSlipTemplate); fEndPoint.fDisconnectSlip:Open(); :MResetQueues(); // blow away all queued data fEndPoint:MDisconnectAction(fromState); :Dirty(); return nil; end, fEndPoint: nil // contains the "live" endpoint while we're connected, nil otherwise , MSetLlamaTalkState: func(newState) begin local appBaseView := GetRoot().(kAppSymbol); appBaseView.fLlamaTalkState := newState; if call kViewIsOpenFunc with (appBaseView) then begin fLlamaTalk:?MLlamaTalk_StateChanged(); if call kViewIsOpenFunc with (fLlamaTalk) then fLlamaTalk:Dirty(); RefreshViews(); end; end, fWriteStream: nil // current outgoing transaction object (a big binary object) , MAbortPendingTransactions: func() begin fInputQueue:MReset(); fOutputQueue:MReset(); return nil; end, MToggle: func() begin if :MIsOpen() then if :MIsVisible() then :Hide() else :Show(); return nil; end, fOutputData: nil // queue of outgoing transaction data items (transaction is sent in chunks) , declareSelf: 'fLlamaTalk, viewSetupFormScript: func() begin if kDebugLlamaTalk then DefGlobalFn('LT_PRINT, EnsureInternal(kLT_PRINT)); self.fEndPointAddress := TotalClone(fEndPointAddress); // ADSP address frame must be in RAM so we can modify it self.fEndPointConfig := fEndPointConfigADSP; // ADSP is the default configuration for this endpoint proto self.fEndPoint := // the endpoint frame must be in RAM so we can modify it { _proto: protoBasicEndpoint, _parent: self, ExceptionHandler: MEndPointExceptionHandler, FInputHeaderComponent: :MProtoClone(FInputHeaderComponent), FInputDataComponent: :MProtoClone(FInputDataComponent), FInputDataSkipper: :MProtoClone(FInputDataSkipper), fConnectAction: nil, fDisconnectSlip: nil, }; self.fInputQueue := :MNewQueue('FIFO); self.fOutputQueue := :MNewQueue('FIFO); end, FInputDataSkipper: { form: 'bytes, termination: { byteCount: 0, }, fDataBytesToGo: 0, inputScript: func(ep, data, terminator, options) begin IF kDebugLlamaTalk THEN LT_PRINT("FInputDataSkipper called..."); fDataBytesToGo := fDataBytesToGo - termination.byteCount; if fDataBytesToGo > 0 then begin termination.byteCount := if fDataBytesToGo < 512 then fDataBytesToGo else 512; ep:SetInputSpec(ep.FInputDataSkipper); end else ep:SetInputSpec(ep.FInputHeaderComponent); end, }, MListen: func() begin IF kDebugLlamaTalk THEN LT_PRINT("LlamaTalk.MListen"); if not :MIsOpen() then // return an error if this view has not yet been opened! return kLlamaTalkError_IllegalOperation; if fLlamaTalkState <> kLlamaTalkState_Disconnected then return kLlamaTalkError_IllegalOperation; :MSetLlamaTalkState(kLlamaTalkState_Listen); fEndPoint:MConnectAction(true, nil); end, MEndPointExceptionHandler: func(exception) begin IF kDebugLlamaTalk THEN LT_PRINT("MEndPointExceptionHandler called!"); if exception.data exists and exception.data <> -16005 // exception generated by ep:Abort() while connected -- really not an error and exception.data <> -16013 // exception generated by ep:Abort() during connect -- really not an error and fLlamaTalkState <> kLlamaTalkState_Disconnecting then begin if exception.data = -20003 // the other end has torn down its connection or exception.data = -16009 then // wouldn't it be nice if all endpoints used the same error codes? if fIsQuietDisconnect then // avoid calling the error method if we're supposed to keep quiet about it nil else fLlamaTalk:?MLlamaTalk_StdError("The connection has closed unexpectedly.", exception.data) else fLlamaTalk:?MLlamaTalk_StdError("A communications error has occured.", exception.data); AddDeferredSend(fLlamaTalk, 'MDisconnect, []); end; true; end, MSetQuietDisconnect: func(quiet) // returns true or nil begin fIsQuietDisconnect := if quiet then true else nil; end, fEndPointAddress: nil // contains the connection address frame, or nil if not required , MIsConnecting: func() begin return (fLlamaTalkState = kLlamaTalkState_Connecting) or (fLlamaTalkState = kLlamaTalkState_Listening); end, MConnectCompProc: // this routine is called from MConnectAction // SELF is the endpoint frame func(options, result) begin IF kDebugLlamaTalk THEN LT_PRINT("LlamaTalk.MConnectCompProc"); if result then begin IF kDebugLlamaTalk THEN LT_PRINT("ERROR in MConnectAction/CompProc, error code =" && NumberStr(result)); if not fIsQuietDisconnect then fLlamaTalk:?MLlamaTalk_StdError("Could not connect.", result); :MDisconnect(); return result; end; if fConnectAction = kLlamaTalkAction_Listen then try :Accept(nil, nil) onexception |evt.ex.comm| do begin fLlamaTalk:?MLlamaTalk_StdError("Could not connect.", CurrentException().error); :MDisconnect(); return; end; :MSetLlamaTalkState(kLlamaTalkState_Connected); :SetInputSpec(FInputHeaderComponent); // kick-off the receive end, FInputDataComponent: { form: 'bytes, termination: { byteCount: 0, }, fDataBuffer: nil, fDataOffset: 0, fDataBytesToGo: 0, inputScript: func(ep, data, terminator, options) begin IF kDebugLlamaTalk THEN LT_PRINT("FInputDataComponent called..."); local size := ep.FInputDataComponent.termination.byteCount; for i := 0 to size - 1 do StuffByte(ep.FInputDataComponent.fDataBuffer, ep.FInputDataComponent.fDataOffset + i, data[i]); ep.FInputDataComponent.fDataOffset := ep.FInputDataComponent.fDataOffset + size; ep.FInputDataComponent.fDataBytesToGo := ep.FInputDataComponent.fDataBytesToGo - size; if ep.FInputDataComponent.fDataBytesToGo > 0 then begin ep.FInputDataComponent.termination.byteCount := if ep.FInputDataComponent.fDataBytesToGo < 512 then ep.FInputDataComponent.fDataBytesToGo else 512; ep:SetInputSpec(ep.FInputDataComponent); end else begin ep._parent.fInputQueue:MEnQueue(ep.FInputDataComponent.fDataBuffer); ep.FInputDataComponent.fDataBuffer := nil; ep:SetInputSpec(ep.FInputHeaderComponent); ep._parent:SetUpIdle(100); end; end, }, viewHideScript: func() begin fBusyShapes := nil; end, viewClass: 74, MListenAsync: func() begin IF kDebugLlamaTalk THEN LT_PRINT("LlamaTalk.MListen"); if not :MIsOpen() then // return an error if this view has not yet been opened! return kLlamaTalkError_IllegalOperation; if fLlamaTalkState <> kLlamaTalkState_Disconnected then return kLlamaTalkError_IllegalOperation; :MSetLlamaTalkState(kLlamaTalkState_Listen); fEndPoint:MConnectAction(true, true); end, fOutputQueue: nil // each entry holds an outgoing transaction (a queue) until it can be processed , MConnectAction: func(isPassive, isAsync) // SELF is the endpoint frame begin IF kDebugLlamaTalk THEN LT_PRINT("LlamaTalk.MConnectAction"); RegPowerOff( kAppSymbol, func(what, why) // we create the closure here so as to set up SELF as the endpoint frame in the closure begin if what = 'okToPowerOff then begin if why <> 'idle // keep the unit awake whenever we're connected or fLlamaTalkState = kLlamaTalkState_Disconnected then // unless the user or an application explicitly return true; // wants it to sleep end; else if what = 'powerOff then begin if why <> 'idle // if we simply must go to sleep but we're still or fLlamaTalkState <> kLlamaTalkState_Disconnected then // connected then begin the disconnect process begin fPowerOffState := 'holdYourHorses; // set a flag to indicate we're powering down :MDisconnect(); return 'holdYourHorses; end; end; nil; // ALWAYS return nil here! end ); if isPassive then fConnectAction := kLlamaTalkAction_Listen; else fConnectAction := kLlamaTalkAction_Connect; :MSetQuietDisconnect(nil); local error; local options := Clone(fEndPointConfig); try options := :Instantiate(self, options) onexception |evt.ex.comm| do begin error := CurrentException().error; IF kDebugLlamaTalk THEN LT_PRINT("ERROR in MConnectAction Instantiate, error code =" && NumberStr(error)); :?MLlamaTalk_StdError(kLlamaTalkMessage_EndpointInUse, error); :MSetLlamaTalkState(kLlamaTalkState_Disconnected); UnRegPowerOff(kAppSymbol); return error; end; try options := :Bind(nil, nil) onexception |evt.ex.comm| do begin error := CurrentException().error; :?MLlamaTalk_StdError(kLlamaTalkMessage_BindFailed, error); :MSetLlamaTalkState(kLlamaTalkState_Disconnected); :Dispose(); UnRegPowerOff(kAppSymbol); return error; end; try begin if fConnectAction = kLlamaTalkAction_Listen then begin :MSetLlamaTalkState(kLlamaTalkState_Listening); options := :Listen( [fEndPointAddress], { async: isAsync, reqTimeout: 90000, // 90 seconds -- for DEMO purposes only completionScript: func(ep, options, result) ep:MConnectCompProc(options, result) }); end; else if fConnectAction = kLlamaTalkAction_Connect then begin :MSetLlamaTalkState(kLlamaTalkState_Connecting); options := :Connect( [fEndPointAddress], { async: isAsync, reqTimeout: 30000, // 30 seconds -- for DEMO purposes only completionScript: func(ep, options, result) ep:MConnectCompProc(options, result), }); end; else Throw('|evt.ex.msg|,"Illegal fConnectAction value (neither kLlamaTalkAction_Connect or kLlamaTalkAction_Listen)."); end onexception |evt.ex.comm| do begin error := CurrentException().error; :MConnectCompProc(options, error); return error; end; if not isAsync then :MConnectCompProc(options, nil); nil; // return "no error" to caller end, MAbortTransaction: func() begin IF kDebugLlamaTalk THEN LT_PRINT("Aborting current transaction..."); return :MBeginTransaction(); end, fInputQueue: nil // holds incoming data until it can be processed , fPowerOffState: nil, viewShowScript: func() begin local r := :LocalBox(); local s := MakeOval(0, 0, r.right - 1, r.bottom - 1); fBusyShapes := [ { fShape: [ MakeShape(r) ], // used to erase the view fStyle: { transferMode: modeCopy, penPattern: vfNone, fillPattern: vfFillWhite, } }, { fShape: [ s, // 'DISCONNECTED phase MakeLine(0, 0, r.right - 1, r.bottom - 1), MakeLine(0, r.bottom - 1, r.right - 1, 0) ], fStyle: { transferMode: modeCopy, penSize: 1, penPattern: vfBlack, fillPattern: vfFillWhite, } }, { fShape: [ s ], // 'IDLE phase fStyle: { transferMode: modeCopy, penSize: 1, penPattern: vfBlack, fillPattern: vfFillWhite, } }, { fShape: [ s ], // 'INPUT phase fStyle: { transferMode: modeCopy, penSize: 1, penPattern: vfBlack, fillPattern: vfFillGray, } }, { fShape: [ s ], // 'OUTPUT phase fStyle: { transferMode: modeCopy, penSize: 1, penPattern: vfNone, fillPattern: vfFillBlack, } }, ]; end, MGetState: func() begin local state := -1; if fLlamaTalkState = kLlamaTalkState_Connected then try state := fEndPoint:State(); onexception |evt.ex| do state := kLlamaTalkError_IllegalOperation; return state; end, fIsQuietDisconnect: nil }; constant |layout_protoLlamaTalk| := LlamaTalk; // End of file protoLlamaTalk // Beginning of file LlamaTalkMain.t // Before Script for "vDali" // This file and project is Copyright © 1994-1995 Apple Computer, Inc. All rights reserved. vDali := {viewFormat: 83951953, viewBounds: {left: 2, top: 2, right: 242, bottom: 338}, 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 )); end, viewSetupDoneScript: func() begin :DoUpdateButtons(); end, DoConnect: func() begin if not vLlamaTalk:MIsOpen() then vLlamaTalk:Open(); if vProtocol.clusterValue = 1 then begin vLlamaTalk:MSetProtocol('ADSP); vLlamaTalk:MSetAddress("Echo Server:EchoLlama@*"); end else begin vLlamaTalk:MSetProtocol('MNP); vLlamaTalk:MSetAddress(nil); end; vLlamaTalk:MSetQuietDisconnect(true); local err := nil; if vAsync.viewValue then if vPassive.viewValue then err := vLlamaTalk:MListenAsync(); else err := vLlamaTalk:MConnectAsync(); else begin if vPassive.viewValue then err := vLlamaTalk:MListen(); else err := vLlamaTalk:MConnect(); end; if err then vLlamaTalk:Close(); :DoUpdateButtons(); end, DoDisconnect: func() begin vLlamaTalk:MDisconnect(); vLlamaTalk:Close(); :DoUpdateButtons(); :DoMessage("Ready for connect..."); end, DoMessage: func(message) begin SetValue(vMessage, 'text, Clone(message)); RefreshViews(); end, DoUpdateButtons: func() begin if vLlamaTalk:MIsConnected() then SetValue(vConnect, 'text, "Disconnect"); else if vPassive.viewValue then begin if vLlamaTalk:MIsConnecting() then SetValue(vConnect, 'text, "Stop Listening"); else if vAsync.viewValue then SetValue(vConnect, 'text, "Listen Async"); else SetValue(vConnect, 'text, "Listen"); end; else begin if vLlamaTalk:MIsConnecting() then SetValue(vConnect, 'text, "Stop Connecting"); else if vAsync.viewValue then SetValue(vConnect, 'text, "Connect Async"); else SetValue(vConnect, 'text, "Connect"); end; if vLlamaTalk:MIsOpen() then begin SetValue(vOpenClose, 'text, "Close"); vShowHide:Show(); if vLlamaTalk:MIsVisible() then SetValue(vShowHide, 'text, "Hide"); else SetValue(vShowHide, 'text, "Show"); end; else begin SetValue(vOpenClose, 'text, "Open"); vShowHide:Hide(); end; end, MLlamaTalk_InputScript: func(data) begin if Length(data) = 0 then return :DoMessage("Empty data packet."); local s, fieldType := ExtractByte(data, 0); if fieldType = kLlamaTalkType_nil then s := "Nil"; else if fieldType = kLlamaTalkType_true then s := "True"; else if fieldType = kLlamaTalkType_char then s := "Char " & $\22 & ExtractChar(data, 1) & $\22; else if fieldType = kLlamaTalkType_integer then s := "Integer " & $\22 & NumberStr(ExtractLong(data, 1)) & $\22; else if fieldType = kLlamaTalkType_real then s := "Real " & $\22 & ExtractPString(data, 1) & $\22; else if fieldType = kLlamaTalkType_string then begin local len := ExtractWord(data, 1); local obj := SetLength(ExtractBytes(data, 3, len, 'binary), len + 1); StuffByte(obj, len, 0); s := "String of length " & NumberStr(len) & " " & $\22 & ExtractCString(obj, 0) & $\22; end; else if fieldType = kLlamaTalkType_binary then begin local len := ExtractLong(data, 1); s := "Binary of length " & NumberStr(len); end; else s := ExtractCString(data, 0); // must be an unformatted packet from LlamaTalkEchoApp :DoMessage("First field in data packet = " & unicodeCR & s); end, MLlamaTalk_StdError: func(messageText, errorNum) begin :DoUpdateButtons(); if errorNum = 0 then local text := messageText else local text := messageText & "\n" & NumberStr(errorNum); :DoMessage(text); GetRoot():Notify(kNotifyAlert, kAppName, text); end, fLlamaTalkState: // this is a REQUIRED slot for use with protoLlamaTalk // it MUST have the initial value of NIL when compiled // it orchestrates the connect and disconnect process nil, MLlamaTalk_StateChanged: func() begin if vLlamaTalk:MIsConnecting() then :DoMessage("Connecting..."); else if vLlamaTalk:MIsConnected() then :DoMessage("Connected"); else :DoMessage("Ready for connect..."); :DoUpdateButtons(); end, title: kAppName, _proto: @157 }; vLlamaTalk := {viewBounds: {left: 8, top: 141, right: 24, bottom: 157}, _proto: LlamaTalk} ; AddStepForm(vDali, vLlamaTalk); StepDeclare(vDali, vLlamaTalk, 'vLlamaTalk); vMessage := {text: "Ready for connect...", viewBounds: {left: 9, top: 25, right: 229, bottom: 125}, viewJustify: 0, viewFormat: 524624, viewFlags: 515, viewClickScript: func(unit) begin SetValue(self, 'text, ""); true; end, _proto: @218 }; AddStepForm(vDali, vMessage); StepDeclare(vDali, vMessage, 'vMessage); vStatus := {text: "", viewBounds: {left: 32, top: 136, right: 224, bottom: 168}, viewIdleScript: func() begin SetValue (vStatus, 'text, "In =" && NumberStr(vLlamaTalk:MCountPendingInputTransactions()) & " " & "Out =" && NumberStr(vLlamaTalk:MCountPendingOutputTransactions()) & "\n" & "State =" && NumberStr(vLlamaTalk:MGetState()) & " " & (if fLlamaTalkState then NumberStr(fLlamaTalkState) else "nil") & " " & "Ticks =" && NumberStr(Ticks()) ); 1000; // 'idle one second': return the number of milliseconds to wait before calling IdelScript again end, viewSetupDoneScript: func() begin :SetUpIdle(100); end, viewJustify: 0, _proto: @218 }; AddStepForm(vDali, vStatus); StepDeclare(vDali, vStatus, 'vStatus); vConnect := {text: "", buttonClickScript: func() begin if vLlamaTalk:MIsConnected() then :DoDisconnect() else :DoConnect(); :DoUpdateButtons(); end, viewBounds: {left: 122, top: 230, right: 222, bottom: 246}, _proto: @226 }; AddStepForm(vDali, vConnect); StepDeclare(vDali, vConnect, 'vConnect); vOpenClose := {text: "", buttonClickScript: func() begin vLlamaTalk:Toggle(); :DoUpdateButtons(); end, viewBounds: {left: 122, top: 254, right: 166, bottom: 270}, _proto: @226 }; AddStepForm(vDali, vOpenClose); StepDeclare(vDali, vOpenClose, 'vOpenClose); vShowHide := {text: "", buttonClickScript: func() begin vLlamaTalk:MToggle(); :DoUpdateButtons(); end;, viewBounds: {left: 178, top: 254, right: 222, bottom: 270}, viewFlags: 515, _proto: @226 }; AddStepForm(vDali, vShowHide); StepDeclare(vDali, vShowHide, 'vShowHide); vSendPings := {text: "Start Ping Test", buttonClickScript: func() begin if fIsActiveIdle then begin fIsActiveIdle := nil; SetValue(self, 'text, "Start Ping Test"); :SetUpIdle(0); end else begin fIsActiveIdle := true; SetValue(self, 'text, "Stop Ping Test"); :SetUpIdle(100); end; end, viewBounds: {left: 122, top: 278, right: 222, bottom: 294}, viewIdleScript: func() begin try begin if vLlamaTalk:MCountPendingOutputTransactions() < 1 then // let's try to not explode our Newton, okay? if vLlamaTalk:MBeginTransaction() then begin vLlamaTalk:MWriteObject("How very like the future this place might be. It is a tiny world just big enough to support the chemicals of one knowledge worker. I feel a wave of loneliness as I head back down. Am I going too fast?"); vLlamaTalk:MWriteObject("I plunge right on in through the office door and into the bottomless sea below. Suddenly I can't remember how to stop. Turn around. Look. Point behind myself. Do I have to turn around and point? I flip into a burning fit."); vLlamaTalk:MEndTransaction(); end else Throw('|evt.ex.msg.ignore.me|, "A ping error occured!"); end onexception |evt.ex.msg.ignore.me| do begin fIsActiveIdle := nil; SetValue(self, 'text, "Start Ping Test"); return nil; end; return 1000; // idle rate at which to send, in miliseconds, or nil to turn off idle handler end, fIsActiveIdle: nil, _proto: @226 }; AddStepForm(vDali, vSendPings); StepDeclare(vDali, vSendPings, 'vSendPings); vProtocolLabel := {text: "Transport Protocol:", viewBounds: {left: 8, top: 176, right: 112, bottom: 192}, viewJustify: 8388609, _proto: @218 }; AddStepForm(vDali, vProtocolLabel); StepDeclare(vDali, vProtocolLabel, 'vProtocolLabel); vProtocol := {viewBounds: {left: 120, top: 178, right: 216, bottom: 192}, clusterValue: 1, _proto: @203 }; AddStepForm(vDali, vProtocol); StepDeclare(vDali, vProtocol, 'vProtocol); vADSP := {buttonValue: 1, viewBounds: {left: 0, top: -5, right: 48, bottom: 11}, text: "ADSP", _proto: @202 }; AddStepForm(vProtocol, vADSP); StepDeclare(vDali, vADSP, 'vADSP); vMNP := {buttonValue: 2, viewBounds: {left: 48, top: -5, right: 96, bottom: 11}, text: "MNP", _proto: @202 }; AddStepForm(vProtocol, vMNP); StepDeclare(vDali, vMNP, 'vMNP); vS := {text: "Str", buttonClickScript: func() begin vLlamaTalk:MBeginTransaction(); vLlamaTalk:MWriteObject("Hello"); vLlamaTalk:MEndTransaction(); end, viewBounds: {left: 66, top: 234, right: 94, bottom: 246}, _proto: @226 }; AddStepForm(vDali, vS); StepDeclare(vDali, vS, 'vS); vC := {text: "Char", buttonClickScript: func() begin vLlamaTalk:MBeginTransaction(); vLlamaTalk:MWriteObject($A); vLlamaTalk:MEndTransaction(); end, viewBounds: {left: 26, top: 234, right: 54, bottom: 246}, _proto: @226 }; AddStepForm(vDali, vC); StepDeclare(vDali, vC, 'vC); vT := {text: "True", buttonClickScript: func() begin vLlamaTalk:MBeginTransaction(); vLlamaTalk:MWriteObject(true); vLlamaTalk:MEndTransaction(); end, viewBounds: {left: 66, top: 210, right: 94, bottom: 222}, _proto: @226 }; AddStepForm(vDali, vT); StepDeclare(vDali, vT, 'vT); vI := {text: "Int", buttonClickScript: func() begin vLlamaTalk:MBeginTransaction(); vLlamaTalk:MWriteObject(1234567); vLlamaTalk:MEndTransaction(); end, viewBounds: {left: 26, top: 258, right: 54, bottom: 270}, _proto: @226 }; AddStepForm(vDali, vI); StepDeclare(vDali, vI, 'vI); vR := {text: "Real", buttonClickScript: func() begin vLlamaTalk:MBeginTransaction(); vLlamaTalk:MWriteObject(1234567.89); vLlamaTalk:MEndTransaction(); end, viewBounds: {left: 66, top: 258, right: 94, bottom: 270}, _proto: @226 }; AddStepForm(vDali, vR); StepDeclare(vDali, vR, 'vR); vN := {text: "Nil", buttonClickScript: func() begin vLlamaTalk:MBeginTransaction(); vLlamaTalk:MWriteObject(nil); vLlamaTalk:MEndTransaction(); end, viewBounds: {left: 26, top: 210, right: 54, bottom: 222}, _proto: @226 }; AddStepForm(vDali, vN); StepDeclare(vDali, vN, 'vN); vB := {text: "Binary", buttonClickScript: func() begin vLlamaTalk:MBeginTransaction(); local b := SetLength(SetClass(Clone(""), 'binary), 4); StuffLong(b, 0, 1234567); vLlamaTalk:MWriteObject(b); vLlamaTalk:MEndTransaction(); end, viewBounds: {left: 34, top: 282, right: 86, bottom: 294}, _proto: @226 }; AddStepForm(vDali, vB); StepDeclare(vDali, vB, 'vB); vAsync := {indent: 4, text: "Connect Asynchronously", viewBounds: {left: 112, top: 208, right: 234, bottom: 224}, viewSetupFormScript: func() begin self.indent := self.indent + StrWidth(self.text); inherited:?viewSetupFormScript(); end, viewValue: nil, valueChanged: func() begin :DoUpdateButtons(); end, _proto: @204 }; AddStepForm(vDali, vAsync); StepDeclare(vDali, vAsync, 'vAsync); vPassive := {indent: 4, text: "Wait For Connection", viewBounds: {left: 112, top: 192, right: 234, bottom: 208}, viewSetupFormScript: func() begin self.indent := self.indent + StrWidth(self.text); inherited:?viewSetupFormScript(); end, viewValue: nil, valueChanged: func() begin :DoUpdateButtons(); end, _proto: @204 }; AddStepForm(vDali, vPassive); StepDeclare(vDali, vPassive, 'vPassive); constant |layout_LlamaTalkMain.t| := vDali; // End of file LlamaTalkMain.t