-- TalkerImpl.mesa -- Paul Rovner, May 4, 1983 5:09 pm -- Last Edited by: Greene, April 20, 1984 3:25:25 pm PST -- Last Edited by: Nickell, June 15, 1984 12:33:13 pm PDT -- Last Edited by: Rumph, June 15, 1984 3:29:29 pm PDT DIRECTORY Booting USING [RegisterProcs, CheckpointProc, RollbackProc], Commander USING [Register, CommandProc], Containers USING [ChildXBound, ChildYBound], DefaultRegistry USING [MakeRegistryExplicit], Labels USING [Label, Create, Set], Icons USING [IconFlavor, NewIconFromFile], MBQueue USING [Queue, Create, CreateMenuEntry, QueueClientAction], IO USING [GetTokenRope, IDProc, PutRope, STREAM, time, char, Put, EndOfStream, RIS], Menus USING[CreateMenu, InsertMenuEntry, Menu, MenuProc], Process USING [Detach], Rope USING[ROPE, Concat, Cat, Equal], RPC USING[MakeKey], TalkerOps USING[], -- EXPORTS ONLY TalkerOpsRpcControl USING[InterfaceRecord, ImportNewInterface, ExportInterface, UnexportInterface], TalkerPrivate USING[Handle, ConversationObject, Conversant, ConversantObject, Puller, SendOutGoingLines, JoinConversation, ComputeStatus, RemakeStatusLabel, PaintLine, KillConversation, IsViewer, BlinkOff, FindConversant, JoinConversationHit, ShowStatusHit, LowerRope, RemoveConversantFromOtherConversations], TypeScript USING [Create, TS], UserCredentials USING [Get], VFonts USING [CharWidth, FontHeight], ViewerClasses USING [Viewer], ViewerEvents USING [EventProc, RegisterEventProc, ViewerEvent], ViewerIO USING [CreateViewerStreams], ViewerOps USING [CreateViewer], File USING [GetVolumeName, SystemVolume]; TalkerImpl: CEDAR MONITOR -- protects "conversations" list IMPORTS Booting, Commander, Containers, DefaultRegistry, Icons, IO, Labels, MBQueue, Menus, Process, Rope, RPC, TalkerOpsRpcControl, TalkerPrivate, TypeScript, UserCredentials, VFonts, ViewerEvents, ViewerIO, ViewerOps, File EXPORTS TalkerOps, TalkerPrivate = BEGIN OPEN Rope, TalkerPrivate; -- VARIABLES conversations: LIST OF Handle _ NIL; talkerIcon: Icons.IconFlavor = Icons.NewIconFromFile["Talker.icons", 0]; viewerEventQueue: MBQueue.Queue = MBQueue.Create[]; -- PROCEDURES -- ******* interface procedures -- registered with Commander Talk: Commander.CommandProc = { --PROC [cmd: Commander.Handle] h: Handle _ NIL; cls: IO.STREAM = IO.RIS[cmd.commandLine]; DO {conversantName: ROPE _ NIL; conversantName _ IO.GetTokenRope[cls, IO.IDProc ! IO.EndOfStream => GOTO endOfStream; ANY => CONTINUE].token; IF conversantName # NIL THEN {conversantName _ DefaultRegistry.MakeRegistryExplicit[conversantName, LIST[$Talk, $Default]]; IF Rope.Equal[conversantName, GetLocalName[], FALSE] THEN GOTO badConversant; IF h = NIL THEN [h, ] _ FindConversation[conversantName ! ANY => GOTO badConversant] ELSE JoinConversation[h, conversantName ! ANY => GOTO badConversant]; RemoveConversantFromOtherConversations[h, conversantName ! ANY => GOTO badConversant]; EXITS badConversant => cmd.out.PutRope [Cat[" ***Talker error: no such conversant: ", conversantName, "\n"] ! ANY => CONTINUE]}; EXITS endOfStream => EXIT} ENDLOOP; IF h # NIL THEN RemakeStatusLabel[h]; }; -- end Talk -- RPC magic. These are the only procs called by remote clients. PutMyLine: PUBLIC PROC[myName: ROPE, line: ROPE] = { h: Handle _ NIL; rePaintStatus: BOOL _ FALSE; [h, rePaintStatus] _ FindConversation[myName ! ANY => CONTINUE]; IF h = NIL THEN RETURN; PaintLine[h, Cat[myName, ": ", line]]; IF rePaintStatus THEN RemakeStatusLabel[h]; }; -- end PutMyLine JoinWith: PUBLIC PROC[myName: ROPE, newConversant: ROPE] = { h: Handle _ NIL; rePaintStatus: BOOL _ FALSE; IF Equal[newConversant, myName, FALSE] THEN RETURN; [h, rePaintStatus] _ FindConversation[myName ! ANY => CONTINUE]; IF h = NIL THEN RETURN; JoinConversation[h, newConversant]; RemakeStatusLabel[h]; RemoveConversantFromOtherConversations[h, newConversant]; RemoveConversantFromOtherConversations[h, myName]; }; -- end JoinWith DealMeOut: PUBLIC PROC[myName: ROPE] = { RemoveConversantFromOtherConversations[NIL, myName]; }; -- end DealMeOut -- ******* menu and event procedures DisconnectHit: Menus.MenuProc = { -- [parent: REF, clientData: REF, mouseButton: MouseButton, shift, control: BOOL] h: Handle _ NARROW[clientData, Handle]; KillConversation[h]; RemakeStatusLabel[h]; }; -- end DisconnectHit VEBundle: TYPE = RECORD[viewer: ViewerClasses.Viewer, event: ViewerEvents.ViewerEvent]; ViewerEvent: ViewerEvents.EventProc = { MBQueue.QueueClientAction [viewerEventQueue, DoViewerEvent, NEW[VEBundle _ [viewer, event]]]; }; DoViewerEvent: ENTRY PROC[r: REF ANY] = { ENABLE UNWIND => NULL; event: ViewerEvents.ViewerEvent; viewer: ViewerClasses.Viewer; [viewer: viewer, event: event] _ NARROW[r, REF VEBundle]^; SELECT event FROM open => {p: PROC[h: Handle] RETURNS[stop: BOOL _ FALSE] = {IF IsViewer[h, viewer] THEN BlinkOff[h]}; [] _ InnerEnumerateConversations[p]; }; destroy => {p: PROC[h: Handle] RETURNS[stop: BOOL _ FALSE] = {IF IsViewer[h, viewer] THEN { prev: LIST OF Handle _ NIL; KillConversation[h]; FOR c: LIST OF Handle _ conversations, c.rest UNTIL c = NIL DO IF c.first = h THEN {IF prev = NIL THEN conversations _ c.rest ELSE prev.rest _ c.rest; EXIT} ELSE prev _ c; ENDLOOP; stop _ TRUE; }; }; [] _ InnerEnumerateConversations[p]; }; ENDCASE }; -- end DoViewerEvent -- ******* construction procedures FindConversation: ENTRY PROC[conversantName: ROPE] RETURNS[h: Handle _ NIL, rePaintStatus: BOOL _ FALSE] = { -- find a conversation with the specified conversant or create a new one ENABLE UNWIND => NULL; p: PROC[ch: Handle] RETURNS[stop: BOOL _ FALSE] = {IF FindConversant[ch, conversantName] # NIL THEN stop _ TRUE}; h _ InnerEnumerateConversations[p]; IF h # NIL THEN RETURN[h, FALSE] ELSE RETURN[NewConversant[conversantName], TRUE]; }; -- end FindConversation NewConversant: INTERNAL PROC[conversantName: ROPE] RETURNS[handle: Handle] = { container: ViewerClasses.Viewer _ NIL; menu: Menus.Menu _ Menus.CreateMenu[lines: 1]; ymax: INTEGER _ 0; emWidth: INTEGER _ VFonts.CharWidth['M]; emHeight: INTEGER _ VFonts.FontHeight[]; ir: TalkerOpsRpcControl.InterfaceRecord _ TalkerOpsRpcControl.ImportNewInterface [interfaceName: [instance: LowerRope[conversantName]]]; AddLabel: PROC [indent: INTEGER _ 0, trail: INTEGER _ 2] RETURNS [label: Labels.Label] = { -- adds a new label to the container at the given position -- returns the button and new position labelMaxChars: NAT _ 64; x: INTEGER _ 0; y: INTEGER _ ymax + 2; w: INTEGER _ emWidth * labelMaxChars; h: INTEGER _ emHeight; label _ Labels.Create [[name: " ", parent: container, border: FALSE, wx: x + indent, wy: y, ww: w, wh: h]]; x _ label.wx + label.ww - 1; y _ label.wy + label.wh - 1 + trail; IF y > ymax THEN ymax _ y; }; -- START NewConversant HERE handle _ NEW[ConversationObject _ [conversants: CONS[NEW[ConversantObject _ [ir: ir, conversantName: conversantName]], NIL ], mbq: MBQueue.Create[], localName: GetLocalName[] ] ]; -- build up the menu Menus.InsertMenuEntry [menu: menu, entry: MBQueue.CreateMenuEntry [q: handle.mbq, name: "Disconnect", proc: DisconnectHit, clientData: handle]]; Menus.InsertMenuEntry [menu: menu, entry: MBQueue.CreateMenuEntry [q: handle.mbq, name: "JoinConversation", proc: JoinConversationHit, clientData: handle]]; Menus.InsertMenuEntry [menu: menu, entry: MBQueue.CreateMenuEntry [q: handle.mbq, name: "ShowStatus", proc: ShowStatusHit, clientData: handle]]; container _ ViewerOps.CreateViewer [flavor: $Container, info: [name: Rope.Concat[conversantName, " conversation" ], column: left, menu: menu, scrollable: FALSE, icon: talkerIcon]]; handle.container _ container; -- status area handle.statusLabel _ AddLabel[]; Labels.Set[handle.statusLabel, ComputeStatus[handle]]; -- typescript area ymax _ ymax + 2; handle.ts _ TypeScript.Create [info: [parent: container, wx: 0, wy: ymax, ww: 64, wh: 64], paint: FALSE]; [handle.tsInStream, handle.tsOutStream] _ ViewerIO.CreateViewerStreams[name: NIL, viewer: handle.ts]; Containers.ChildXBound[container, handle.ts]; Containers.ChildYBound[container, handle.ts]; conversations _ CONS[handle, conversations]; TRUSTED {Process.Detach[FORK Puller[handle]]}; TRUSTED {Process.Detach[FORK SendOutGoingLines[handle, handle.conversants.first]]}; handle.tsOutStream.Put[IO.time[], IO.char['\n]]; }; -- end NewConversant -- ******* enumeration procedures NewConversation: ENTRY PROC[h: Handle] = { ENABLE UNWIND => NULL; conversations _ CONS[h, conversations]; }; -- end NewConversation EnumerateConversations: PUBLIC PROC[proc: PROC[Handle] RETURNS[stop: BOOL]] RETURNS[Handle] = { FOR h: Handle _ FirstConversation[], NextConversation[h] UNTIL h = NIL DO IF proc[h] THEN RETURN[h]; ENDLOOP; RETURN[NIL]; }; -- end EnumerateConversations InnerEnumerateConversations: INTERNAL PROC[proc: PROC[Handle] RETURNS[stop: BOOL]] RETURNS[ans: Handle _ NIL] = { FOR cl: LIST OF Handle _ conversations, cl.rest UNTIL cl = NIL DO IF proc[cl.first] THEN RETURN[cl.first]; ENDLOOP; }; -- end InnerEnumerateConversations FirstConversation: ENTRY PROC RETURNS[Handle] = { ENABLE UNWIND => NULL; IF conversations = NIL THEN RETURN[NIL]; RETURN[conversations.first]; }; -- end FirstConversation NextConversation: ENTRY PROC[h: Handle] RETURNS[Handle] = { ENABLE UNWIND => NULL; FOR cl: LIST OF Handle _ conversations, cl.rest UNTIL cl = NIL DO IF cl.first = h THEN {IF cl.rest # NIL THEN RETURN[cl.rest.first] ELSE RETURN[NIL]}; ENDLOOP; RETURN[NIL]; }; -- end NextConversation CheckpointProc: Booting.CheckpointProc = { Finalize[]; }; RollbackProc: Booting.RollbackProc = { Initialize[]; }; Initialize: ENTRY PROC = { ENABLE UNWIND => NULL; IF punt THEN punt _ FALSE ELSE RETURN; TRUSTED{ TalkerOpsRpcControl.ExportInterface [interfaceName: [instance: LowerRope[GetLocalName[]]], user: GetLocalName[], password: RPC.MakeKey[UserCredentials.Get[].password] ! ANY => {punt _ TRUE; CONTINUE}]}; IF NOT punt THEN { Commander.Register ["Talk", Talk, "Create an interactive, 2-way typescript with other Cedar users"]; }; }; Finalize: ENTRY PROC = { ENABLE UNWIND => NULL; IF punt THEN RETURN ELSE punt _ TRUE; TRUSTED{TalkerOpsRpcControl.UnexportInterface[! ANY => CONTINUE]}; }; LocalNameChanged: PROC[oldName: REF ANY] = { Finalize[]; Initialize[]; }; GetLocalName: PUBLIC PROC RETURNS[ROPE] = { ln: ROPE _ UserCredentials.Get[].name; IF Equal[localName, ln, FALSE] THEN RETURN[localName] ELSE { MBQueue.QueueClientAction [viewerEventQueue, LocalNameChanged, localName]; localName _ ln; RETURN[localName]; }; }; -- START HERE punt: BOOL _ TRUE; -- FALSE after start iff ExportInterface succeeded and is in effect localName: ROPE _ UserCredentials.Get[].name; IF Rope.Equal["Cedar", File.GetVolumeName[File.SystemVolume[]] ] THEN { Initialize[]; [] _ ViewerEvents.RegisterEventProc[proc: ViewerEvent, event: open, filter: $Container]; [] _ ViewerEvents.RegisterEventProc[proc: ViewerEvent, event: destroy, filter: $Container]; TRUSTED {Booting.RegisterProcs[c: CheckpointProc, r: RollbackProc]}; }; END. PROC [viewer: ViewerClasses.Viewer, event: ViewerEvents.ViewerEvent]; First, establish the user at this Cedar workstation as an available conversant. Next, register a "talk person" command with Commander Next, register "EventProc" with Viewers Next, register with CedarSnapshot Ê [– "Mesa" style˜procšœÏcªœ8Ïk œ žœ<žœ'žœ/žœ"žœžœ*žœ8žœžœ!žœ žœ žœ9žœžœžœžœžœžœœžœ~žœ˜žœ žœžœžœ*žœžœ9žœ$žœžœ-žœžœ!œžœ9žœ6žœ‚žœžœžœ œžœžœ žœ†œ œœÏbœ!œžœ žœžœžœžœžœžœžœžœžœ žœžœ9žœžœžœžœ žœSžœ%žœ,žœžœžœ!žœžœžœRžœžœ"žœMžœžœ„žœžœ"žœ¢žœžœ žœžœžœžœžœžœÏiœ AœÏn œžœžœ žœžœžœžœžœ5žœžœžœžœžœžœ1žœžœœÐbnœžœžœ žœžœžœžœžœžœžœžœžœ5žœžœžœžœžœžœÁ œ¢ œžœžœ žœ1žœ  œ%œŸ œRœžœQ˜ÿ'Iunit˜Lšœ žœžœ@˜WšŸ œ˜'LšœE™Ešœ˜Lšœ˜Lšœ˜Lšœžœ˜"—Lšœ˜——˜š ¢ œžœžœžœžœ˜)Lšžœžœžœ˜Lšœ ˜ Lšœ˜Lšœ!žœžœ ˜:šžœž˜šœ˜š œžœ žœžœžœ˜1Lšœžœžœ˜*—Lšœ%˜%Lšœ˜—šœ ˜ š œžœ žœžœžœ˜1šœžœ˜šžœ˜Lšœžœžœ žœ˜Lšœ˜š žœžœžœ žœž˜;šž˜Lšžœ ˜šž˜šœžœž˜Lšžœ˜Lšžœ˜—Lšžœ˜—Lšžœ ˜—Lšžœ˜—Lšœž˜ Lšœ˜——Lšœ˜—Lšœ%˜%Lšœ˜——Lšž˜Lšœ˜——L˜Lš¼#œ¡œž œžœ žœ žœžœžœ Iœžœžœžœ žœ žœžœžœ žœ&žœžœžœ/žœžœžœžœžœžœžœ žœ œ¡ œž œžœžœ;žœ?žœžœ'žœÙ¡œžœžœ žœžœ!;œ'œžœžœžœžœ%žœižœ–žœ žœ   œ žœ=žœžœìžœÝœ›žœaœfœªžœcžœžœ"žœžœžœžœœ"œ¡œžœžœžœžœžœžœ œ¡œž œžœ žœžœ žœžœ6žœžœžœžœ žœžœ žœžœžœ  œ¡œž œžœ žœžœ žœžœ žœžœžœ!žœžœžœžœžœžœžœ  œ¡œžœžœžœžœžœžœžœžœžœžœžœžœ œ¡œžœžœ žœžœžœžœžœžœžœ!žœžœžœžœžœžœ žœžœžœžœžœžœ žœžœžœ  ˜ª+L˜š¢œ˜*Lšœ ˜ Lšœ˜—L˜š¡ œ˜&Lšœ ˜ Lšœ˜—L˜š¡ œž œ˜Lšžœžœžœ˜Lšžœžœžœ˜&L˜LšœO™Ošžœ˜šœ#˜#Lšœ6˜6Lšœ˜Lšœ žœ(˜6Lšœžœ žœžœ˜#——šžœžœžœ˜Lšœ5™5šœ˜Lšœ˜Lšœ˜LšœC˜C—Lšœ˜—Lšœ˜—L˜šŸœž œ˜Lšžœžœžœ˜Lš žœžœžœžœžœ˜%Lšžœ)žœžœ˜BLšœ˜—L˜š¢œžœ žœžœ˜,Lšœ ˜ Lšœ ˜ Lšœ˜L˜—š ¡ œžœžœžœžœ˜+Lšœžœ˜&šžœžœ˜šž˜Lšžœ ˜—šžœ˜šœ˜Lšœ˜Lšœ˜Lšœ ˜ —Lšœ˜Lšžœ ˜Lšœ˜——Lšœ˜L˜—Lš ˜ LšœžœžœC˜WL˜Lšœ žœ˜-L˜šžœ>˜@šžœ˜Lšœ ˜ Lšœ'™'LšœX˜XLšœ[˜[Lš !™!Lšžœ=˜DLšœ˜——L˜Lšžœ˜—…—3”>