'From Croqtober of 11 November 2005 [latest update: #159] on 19 February 2006 at 7:17:18 pm'! MTablet subclass: #MDesktopTablet instanceVariableNames: 'urlString desktopId ' classVariableNames: '' poolDictionaries: '' category: 'Croquet-MVCR'! Object subclass: #RFBClient instanceVariableNames: 'target image socket sendLock state process protocolMinor serverExtent serverFormat serverName updateRequestPending currentCursor savedCursor hasCursor modifierState zlibStream logClient complained ' classVariableNames: 'CommandKeySym CtrlKeySym DefaultEncoding Enable8Bit EnableExpandOnBell EnableExpandOnConnection EnableShared EnableViewOnly EnableXCursor Encodings FastUpdate KeySyms MessageTypes ModifierMap OptionKeySym ProtocolMajor ProtocolMinor RfbEncodingAuto RfbEncodingCoRRE RfbEncodingCopyRect RfbEncodingHextile RfbEncodingLastRect RfbEncodingPointerPos RfbEncodingRRE RfbEncodingRaw RfbEncodingRichCursor RfbEncodingTight RfbEncodingXCursor RfbEncodingZRLE RfbEncodingZlib RfbEncodingZlibHex ShiftKeySym ' poolDictionaries: 'EventSensorConstants ' category: 'RFB-Viewer'! TMorphic subclass: #TXrfbMorphic instanceVariableNames: 'XrfbClient ' classVariableNames: '' poolDictionaries: '' category: 'Croquet-Xrfb'! VTablet subclass: #VDesktopTablet instanceVariableNames: 'url desktopId ' classVariableNames: '' poolDictionaries: '' category: 'Croquet-MVCR'! AlignmentMorphBob1 subclass: #XrfbBasePanelMorph instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Croquet-Xrfb'! !XrfbBasePanelMorph commentStamp: 'strick 2/11/2006 00:56' prior: 0! XrfbBasePanelMorph is the base for the Control Panel and Server Configuration ! RFBClient subclass: #XrfbClient instanceVariableNames: 'password ' classVariableNames: '' poolDictionaries: '' category: 'Croquet-Xrfb'! XrfbBasePanelMorph subclass: #XrfbControlPanelMorph instanceVariableNames: 'listButton cleanButton shutdownButton textArea ' classVariableNames: '' poolDictionaries: '' category: 'Croquet-Xrfb'! !XrfbControlPanelMorph commentStamp: 'strick 2/10/2006 23:10' prior: 0! XrfbControlPanel -- A Morph for controlling the Xrfb Desktop Server daemon. (* Much of this code stolen from TLoginMorph. Thanks y'all. *)! Error subclass: #XrfbHttpReplyException instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Croquet-Xrfb'! XrfbBasePanelMorph subclass: #XrfbServerConfigMorph instanceVariableNames: 'hostBox portBox passwordBox ' classVariableNames: '' poolDictionaries: '' category: 'Croquet-Xrfb'! Object subclass: #XrfbServerMaker instanceVariableNames: 'outerMorph teaWorld world ' classVariableNames: 'HttpServerHostAddress HttpServerPassword HttpServerPortNumber ' poolDictionaries: '' category: 'Croquet-Xrfb'! !XrfbServerMaker commentStamp: 'strick 1/17/2006 01:11' prior: 0! RFBServerMaker is the squeak client object that makes HTTP requests to the VncServer.py (currently an HTTP server written in Python), which allocates RFB servers for the sake of VDesktopTablet. The four services available: * rpcEcho: aString -- just for testing * rpcLaunchServerForDocumentUrl: aUrlString objectId: anIdString -- request launching a new server (or connecting to existing one) * rpcListServers -- status display * rpcShutdownAllServers -- shut down everything Configure the location of the server with these hardcoded methods, or define them to get from somewhere else: * httpServerHostAddress -- answers the host address, as a string * httpServerPortNumber -- answers the port number, as an Integer Internally, requests are made with a HTTP GET command, with one verb '/echo', '/launch', '/list', or '/shutdown', and any arguments are in the query portion of the URL. Results are in trivial XML, with either #answer tags or #error tags at the second level, e.g. ' 42 Server cannot fork another process ' ! TestCase subclass: #XrfbServerMakerTests instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Croquet-Xrfb'! !XrfbServerMakerTests commentStamp: 'strick 1/17/2006 01:14' prior: 0! SUnit Tests for RfbServerMaker. PRINT IT: RfbServerMakerTests buildSuite run PREREQUISITE: An VncServer.py (currently written in Python) must be running at the address returned by RfbServerMaker>>httpServerHostAddress and RfbServerMaker>>httpServerPortNumber. ! !CroquetObjects class methodsFor: 'objects' stamp: 'strick 2/8/2006 21:39'! makeDesktop: urlString ^ VDesktopTablet new initialize ; modelUrl: urlString desktopId: nil ; "random id will be assigned" yourself ! ! !CroquetObjects class methodsFor: 'objects' stamp: 'strick 2/11/2006 04:05'! makeRFBViewer: url objectId: objectId | tm pageExtent teaWorld world morph client hostAddr hostPort socket displayNumber msg textMorph | " FileDirectory deleteFilePath: 'home.c3d.'. HedgemineMorph new openInWorld " "strick TODO: a better way than hardwiring 800x600" pageExtent := 800@600. tm _ TXrfbMorphic new initializeOpaque: true extent: pageExtent; yourself. teaWorld _ TMorphMonitor new initializeWithWorld: nil extent: pageExtent; yourself. teaWorld eventsTo: tm. world := teaWorld world. Transcript cr; show: 'makeRFBViewer: ', url, ' objectId: ', objectId. [ displayNumber := XrfbServerMaker new rpcLaunchServerForDocumentUrl: url objectId: objectId. ] on: XrfbHttpReplyException do: [ :ex | "Could not talk to Xrfb Server -- bad host, port, password, or it is not running" textMorph := TextMorph new beAllFont: ((TextStyle named: #ComicBold) fontOfSize: 32); backgroundColor: Color yellow; crAction: (MessageSend receiver: self selector: #yourself); extent: pageExtent; contentsWrapped: url, ' ================================== ',ex messageText asText; setBalloonText: 'This decribes the error received when trying to connect to the Xrfb Desktop Server'; centered; yourself. world addMorph: textMorph. ^ tm ]. [ hostAddr := NetNameResolver addressForName: XrfbServerMaker new httpServerHostAddress timeout: 8. hostPort := 5900 + displayNumber. socket _ RFBClientSocket newTCP. socket connectTo: hostAddr port: hostPort. client := XrfbClient new setPassword: XrfbServerMaker new httpServerPassword; yourself. client target: teaWorld. "????" tm xrfbClient: client. "<-----" morph := client asMorph. morph beSticky. world addMorph: morph. ] on: Error do: [ :ex | msg := url, ' ================================== An error occurred while trying to connect to the Xvnc desktop at host ', hostAddr asString, ' port ', hostPort asString, ' --------------- ', ex asString, ' --------------- ', ex messageText. textMorph := TextMorph new beAllFont: ((TextStyle named: #ComicBold) fontOfSize: 32); backgroundColor: Color yellow; crAction: (MessageSend receiver: self selector: #yourself); extent: pageExtent; contentsWrapped: msg asText; setBalloonText: 'This decribes the error received when trying to connect to the Xrfb Desktop Server'; centered; yourself. world addMorph: textMorph. ^ tm ]. client connectTo: socket. client isConnected ifFalse:[ " a little cleanup " socket ifNotNil:[socket destroy]. morph delete. msg := url, ' ================================== RFBClient failed to connect.'. textMorph := TextMorph new beAllFont: ((TextStyle named: #ComicBold) fontOfSize: 32); backgroundColor: Color yellow; crAction: (MessageSend receiver: self selector: #yourself); extent: pageExtent; contentsWrapped: msg asText; setBalloonText: 'This decribes the error received when trying to connect to the Xrfb Desktop Server'; centered; yourself. world addMorph: textMorph. ^ tm ]. morph bounds: world bounds. morph visible: true. ^tm. ! ! !HedgemineMenuBar methodsFor: 'menu' stamp: 'strick 2/11/2006 00:36'! menuBarToolsMenu | menu morph | morph := TeapotMorph current. menu := CMenu new. menu add: 'Paste from clipboard to space' target: self action: #paste. menu addSeparator. menu add: 'Toggle Video' target: self action: #menuToggleVideo disabled: true. menu add: 'Snapshot' target: self action: #menuSnapshot disabled: false. menu add: 'Toggle Full Screen' target: morph action: #toggleFullScreen disabled: false. menu add: 'Terrain Tool' target: self action: #terrainTool disabled: true. menu add: 'Toggle Audio Chat' target: self action: #menuToggleAudio disabled: true. menu add: 'New Chat Window' target: self action: #chatWindow disabled: false. menu add: 'Xrfb Desktops Control Panel' target: self action: #xrfbControlPanel disabled: false. ^menu ! ! !HedgemineMenuBar methodsFor: 'menu' stamp: 'strick 2/11/2006 01:35'! xrfbControlPanel XrfbControlPanelMorph new openInWorld comeToFront! ! !MObject methodsFor: '*Strick' stamp: 'strick 2/19/2006 15:02'! printOn: aStream | aName | super printOn: aStream. (aName := self knownName) notNil ifTrue: [aStream nextPutAll: '<' , aName , '>']. aStream nextPutAll: '('. aStream print: self identityHash; nextPutAll: ')'! ! !MDesktopTablet methodsFor: 'accessing' stamp: 'strick 2/8/2006 18:44'! desktopId ^ desktopId! ! !MDesktopTablet methodsFor: 'accessing' stamp: 'strick 2/8/2006 20:19'! desktopId: anId desktopId := anId. Transcript cr; show: 'MDesktopTablet>>desktopId <- ', desktopId.! ! !MDesktopTablet methodsFor: 'accessing' stamp: 'strick 2/19/2006 16:07'! url: aString urlString _ aString. Transcript cr; show: self asString, ' >>url <- ', aString.! ! !MDesktopTablet methodsFor: 'initialize' stamp: 'strick 2/19/2006 18:57'! initializeView | tmpLabel | self flag: #FIXME. "There's a bug in TWindow that I don't understand and which we need to fix. It get's all confused about the #contents and #rectFront if they're not set in the right order when in the right state. My temporary hack workaround is let the label remain unitialized by super initializeView, and then set it ourselves after the url." Transcript cr; show: '### MDesktopTablet ', self asString, ' initializeView ', Time millisecondClockValue asString, ' ###'. tmpLabel _ label. label _ nil. super initializeView. view modelUrl: self url desktopId: self desktopId. label _ tmpLabel. view modelLabel: label. ! ! !RFBClient methodsFor: 'sending' stamp: 'strick 2/16/2006 17:36'! sendData: aMessage "Send aMessage to the server. Assure mutually-exclusive access to the socket." complained ifNil: [ sendLock critical: [[socket sendData: aMessage] on: Exception do: [:ex| complained := true. self log: ex printString; abort]] ]. ! ! !Socket methodsFor: 'waiting' stamp: 'strick 2/19/2006 18:55'! waitForSendDoneFor: timeout "Wait up until the given deadline for the current send operation to complete. Return true if it completes by the deadline, false if not." | sendDone deadline | deadline := Socket deadlineSecs: timeout. [self isConnected & (sendDone _ self primSocketSendDone: socketHandle) not "Connection end and final data can happen fast, so test in this order" and: [Time millisecondClockValue < deadline]] whileTrue: [ self writeSemaphore waitTimeoutMSecs: (deadline - Time millisecondClockValue)]. sendDone ifFalse: [ Transcript cr; show: 'DEADLINE=', deadline asString, ' NOW=', Time millisecondClockValue asString. ]. ^ sendDone! ! !RFBSocket methodsFor: 'running' stamp: 'strick 2/19/2006 18:59'! runSafely: aBlock "Repeatedly execute aBlock until the connection failes or an error is signaled. If debugging is enabled in RFBServer, open a debugger on the error context for the purposes of developer enlightenment." [[[self isValid and: [self isConnected]] whileTrue: aBlock] on: Error do: [:ex | Transcript cr; show: Time millisecondClockValue asString, ' RFB: caught ' , ex printString. RFBServer debugging ifTrue: [Transcript endEntry. self halt]]] ensure: [self closeAndDestroy]! ! !TFarRef methodsFor: 'accessing' stamp: 'strick 2/19/2006 17:42'! printOn: aStream | | super printOn: aStream. aStream nextPutAll: '('; print: myValue asString; nextPutAll: ')'. ! ! !TObject methodsFor: '*Strick' stamp: 'strick 2/19/2006 15:00'! printOn: aStream | aName | super printOn: aStream. (aName := self knownName) notNil ifTrue: [aStream nextPutAll: '<' , aName , '>']. aStream nextPutAll: '('. aStream print: self identityHash; nextPutAll: ')'! ! !TDesktopTablet methodsFor: 'as yet unclassified' stamp: 'strick 2/15/2006 17:56'! removePortalFrames Transcript cr; show: '### TDesktopTablet removePortalFrames ###'. ^ super removePortalFrames ! ! !TXrfbMorphic methodsFor: 'as yet unclassified' stamp: 'Ryan 2/11/2006 03:40'! handleEvent2D: someEvent pointer: pointer "Transcript cr; show: 'SomeEvent: ', someEvent, 'pointer: ',pointer." "super handleEvent2D: someEvent pointer: pointer." someEvent isMouse ifTrue: [ XrfbClient processModifiers: someEvent buttons. XrfbClient sendPointerEvent: someEvent buttons position: someEvent position. ]. someEvent isKeyboard ifTrue: [ someEvent type == #keyDown ifTrue:[XrfbClient keyDown: someEvent]. someEvent type == #keystroke ifTrue:[XrfbClient keyStroke: someEvent]. someEvent type == #keyUp ifTrue:[XrfbClient keyUp: someEvent]. ].! ! !TXrfbMorphic methodsFor: 'as yet unclassified' stamp: 'Ryan 2/11/2006 03:33'! xrfbClient: anXrfbClient XrfbClient _ anXrfbClient.! ! !TranscriptStream methodsFor: 'access' stamp: 'strick 2/19/2006 15:34'! characterLimit "Tell the views how much to retain on screen" ^ 99999 "strick<<20000"! ! !VObject methodsFor: 'actions' stamp: 'strick 2/8/2006 22:15'! addDesktop: url | tablet | tablet _ VDesktopTablet new. tablet modelUrl: url desktopId: nil. ( url asString beginsWith: 'desktop:' ) ifTrue: [ tablet label: 'Desktop' ] ifFalse: [ tablet label: (url authority isEmpty ifTrue: [url path last] ifFalse: [url authority]). ]. self positionNewObject: tablet.! ! !VObject methodsFor: 'actions' stamp: 'strick 2/8/2006 22:11'! addImport: url | extension | url ifNil: [^nil]. "cancelled" (url asString beginsWith: 'desktop:') ifFalse: [ extension _ url path last asUppercase. "Should really use MIME type..." ((extension endsWith: '.BMP') or: [extension endsWith: '.JPG'] or: [extension endsWith: '.GIF'] or: [extension endsWith: '.PNG']) ifTrue: [^self addMaterial: url]. "Should handle .WAV, .MP3, etc. here." ((extension endsWith: '.WAV') or: [extension endsWith: '.MP3'] or: [extension endsWith: '.AIF']) ifTrue: [^self addSound: url]. (extension endsWith: '.TEA') ifTrue: [^self importMesh: url as: #tea]. (extension endsWith: '.MDL') ifTrue: [^self importMesh: url as: #mdl]. ((extension endsWith: '.WRL') or: [extension endsWith: '.VRML']) ifTrue: [^self importMesh: url as: #vrml]. (extension endsWith: '.ASE') ifTrue: [^self importMesh: url as: #ase]. ]. "If we can't import the content 'natively', then ask some OS desktop to do it for us." "Note that the contents of the URL are not really imported into Croquet. The only thing we persist within Croquet is the URL namestring. If the server specified by the URL goes away, we loose." self addDesktop: url. ! ! !VDesktopTablet methodsFor: 'initialize' stamp: 'strick 2/16/2006 17:33'! initializeModel: aModel recurseWith: sel "Answer all of the promises to wait for to ensure that the model is completely (recursively) initialized." | promises | Transcript cr; show: '### VDesktopTablet ', self asString, ' initializeModel: ', aModel asString, ' recurseWith: ', sel asString, ' ###'. promises := super initializeModel: aModel recurseWith: sel. promises add: (aModel future url: self url asString). promises add: (aModel future desktopId: self desktopId). ^ promises! ! !VDesktopTablet methodsFor: 'accessing' stamp: 'strick 2/16/2006 17:56'! asString ^ super asString, '(', self identityHashPrintString, ')'! ! !VDesktopTablet methodsFor: 'accessing' stamp: 'strick 2/8/2006 18:49'! desktopId ^ desktopId! ! !VDesktopTablet methodsFor: 'accessing' stamp: 'strick 2/16/2006 17:55'! modelUrl: aUrlNameString desktopId: anId | on morphic | Transcript cr; show: '### VDesktopTablet ', self asString, ' modelUrl: ', aUrlNameString asString, ' desktopId: ', anId asString, ' ###'. url _ aUrlNameString asUrl. anId ifNotNil: [ desktopId := anId ]. desktopId ifNil: [ self setRandomDesktopId ]. self flag: #FIXME. "TWindow gets confused when we try to set contents: in the wrong state. It would be better to fix this properly rather than use this kludge." on _ self primitiveGeometry contentsOn. on ifTrue: [self primitiveGeometry primitiveCloseContents]. true ifTrue: [ "************ NEW: with Xrfb " morphic := CroquetObjects makeRFBViewer: url toText objectId: desktopId asString. ] ifFalse: [ "*************** OLD: with Flash & Scamper " (url path last asUppercase endsWith: '.SWF') ifTrue: [morphic _ CroquetObjects makeFlashMorphicFromStream: url retrieveContents contentStream extent: 720@480.] ifFalse: [morphic _ CroquetObjects makeWebPage: url extent: 720@480]. ]. "morphic inspect." self primitiveGeometry contents: morphic. on ifTrue: [self primitiveGeometry primitiveOpenContents]. ! ! !VDesktopTablet methodsFor: 'accessing' stamp: 'strick 2/8/2006 20:17'! setRandomDesktopId desktopId := (UUIDGenerator default generateRandomBitsOfLength: 64) asString. Transcript cr; show: 'VDesktopTablet>>setRandomDesktopId <- ', desktopId. ! ! !VDesktopTablet methodsFor: 'finalization' stamp: 'strick 2/15/2006 17:36'! closeContents Transcript cr; show: '### VDesktopTablet closeContents ###'. ^ super closeContents ! ! !VDesktopTablet methodsFor: 'finalization' stamp: 'strick 2/15/2006 16:37'! destroy Transcript cr; show: '### VDesktopTablet destroy ###'. ^ super destroy ! ! !VDesktopTablet methodsFor: 'finalization' stamp: 'strick 2/15/2006 16:55'! finalize " Smalltalk garbageCollect. " Transcript cr; show: '### VDesktopTablet finalize ###'. ^ super finalize ! ! !VDesktopTablet methodsFor: 'finalization' stamp: 'strick 2/15/2006 16:49'! removeAll Transcript cr; show: '### VDesktopTablet removeAll ###'. ^ super removeAll ! ! !VDesktopTablet methodsFor: 'finalization' stamp: 'strick 2/16/2006 17:58'! removePortalFrames Transcript cr; show: '### VDesktopTablet ', self asString, ' removePortalFrames ###'. ^ super removePortalFrames ! ! !VDesktopTablet methodsFor: 'as yet unclassified' stamp: 'strick 2/19/2006 15:40'! copyToClipboard Transcript cr; show: '@ ', self asString, ' copyToClipboard BEGIN'. super copyToClipboard. Transcript cr; show: '@ ', self asString, ' copyToClipboard END'.! ! !VDesktopTablet methodsFor: 'as yet unclassified' stamp: 'strick 2/19/2006 15:42'! pasteFromClipboard Transcript cr; show: '@ ', self asString, ' pasteFromClipboard BEGIN'. super pasteFromClipboard. Transcript cr; show: '@ ', self asString, ' pasteFromClipboard END'.! ! !XrfbBasePanelMorph methodsFor: 'as yet unclassified' stamp: 'strick 2/11/2006 01:00'! buttonColor ^color darker! ! !XrfbBasePanelMorph methodsFor: 'as yet unclassified' stamp: 'strick 2/11/2006 00:57'! buttonNamed: aString action: aSymbol color: aColor help: helpString | f col | f _ SimpleButtonMorph new target: self; label: aString font: self myFont; color: aColor; actionSelector: aSymbol; setBalloonText: helpString. f width: 10 * aString size + 10. f cornerStyle: #square. f color: (Color r: 0.95 g: 0.95 b: 1.0). f borderStyle: (BorderStyle raised width: 2). col _ (self inAColumn: {f}) hResizing: #spaceFill. ^col! ! !XrfbBasePanelMorph methodsFor: 'as yet unclassified' stamp: 'strick 2/11/2006 01:39'! closeButton ^self buttonNamed: 'X' action: #delete color: self buttonColor help: 'Close the Control Panel'! ! !XrfbBasePanelMorph methodsFor: 'as yet unclassified' stamp: 'strick 2/11/2006 00:59'! initialize " self new openInWorld " | fs | super initialize. self beSticky. fs _ GradientFillStyle ramp: {0.0 -> (Color r: 0.8 g: 0.8 b: 1.0). 1.0 -> (Color white) }. self vResizing: #shrinkWrap. self hResizing: #shrinkWrap. color _ Color paleYellow. borderWidth _ 4. borderColor _ #complexRaised. self layoutInset: (4@6). self rebuild: ' '. fs origin: bounds origin. fs direction: 0@self fullBounds height. self fillStyle: fs.! ! !XrfbBasePanelMorph methodsFor: 'as yet unclassified' stamp: 'strick 2/11/2006 01:02'! maker ^ XrfbServerMaker new! ! !XrfbBasePanelMorph methodsFor: 'as yet unclassified' stamp: 'strick 2/11/2006 01:02'! myFont ^(TextStyle named: #ComicBold) fontOfSize: 16! ! !XrfbClient methodsFor: 'as yet unclassified' stamp: 'strick 2/19/2006 18:27'! abort self zork. super abort.! ! !XrfbClient methodsFor: 'as yet unclassified' stamp: 'strick 2/15/2006 16:21'! disconnect "Close the new connection. In our super, it uses #inform: and #confirm: -- we omit those, and just abort if connected. " self isConnected ifTrue: [ self abort ] ! ! !XrfbClient methodsFor: 'as yet unclassified' stamp: 'strick 2/7/2006 16:31'! requestPassword: label "the challenge-response encryption ALTERS the string it is passed, so we must create a new String instance here. This seems to do it." ^ '', password. ! ! !XrfbClient methodsFor: 'as yet unclassified' stamp: 'strick 2/7/2006 16:28'! setPassword: aString password := aString! ! !XrfbControlPanelMorph methodsFor: 'as yet unclassified' stamp: 'strick 2/11/2006 00:23'! cleanButton ^self buttonNamed: 'Clean up' action: #doClean color: self buttonColor help: 'Cleanup unused desktops on the Xrfb Desktop Server'! ! !XrfbControlPanelMorph methodsFor: 'as yet unclassified' stamp: 'strick 2/11/2006 01:40'! configureButton ^self buttonNamed: 'Configure...' action: #doConfigure color: self buttonColor help: 'Configure the hostname, port number, and shared password for the server'! ! !XrfbControlPanelMorph methodsFor: 'as yet unclassified' stamp: 'strick 2/11/2006 19:28'! doClean self doSomething: 'clean' ! ! !XrfbControlPanelMorph methodsFor: 'as yet unclassified' stamp: 'strick 2/11/2006 01:47'! doConfigure XrfbServerConfigMorph new openInWorld comeToFront. self rebuild: '...' ! ! !XrfbControlPanelMorph methodsFor: 'as yet unclassified' stamp: 'strick 2/11/2006 19:27'! doList self doSomething: 'list' ! ! !XrfbControlPanelMorph methodsFor: 'as yet unclassified' stamp: 'strick 2/11/2006 19:28'! doShutdown self doSomething: 'shutdown' ! ! !XrfbControlPanelMorph methodsFor: 'as yet unclassified' stamp: 'strick 2/11/2006 19:32'! doSomething: what "set a message while we are working" self rebuild: '... sending request: ', what, ' ...' . self refreshWorld. "do the work" self rebuild: (self maker doSomething: what) ! ! !XrfbControlPanelMorph methodsFor: 'as yet unclassified' stamp: 'strick 2/10/2006 22:55'! listButton ^self buttonNamed: 'List Desktops' action: #doList color: self buttonColor help: 'List all desktops and connections on the Xrfb Desktop Server'! ! !XrfbControlPanelMorph methodsFor: 'as yet unclassified' stamp: 'strick 2/11/2006 03:57'! rebuild: someText | label | label := ' Desktops on ', XrfbServerMaker new httpServerHostAddress asString. self removeAllMorphs. self addARow: { self closeButton. (StringMorph contents: label ) lock }. self addARow: { self listButton. self cleanButton. self configureButton. self shutdownButton. }. self addARow: { (textArea := TextMorph new beAllFont: self myFont; crAction: (MessageSend receiver: self selector: #doOK); extent: 600@40; contentsWrapped: someText asText; setBalloonText: 'Results are displayed here.' ) }. ! ! !XrfbControlPanelMorph methodsFor: 'as yet unclassified' stamp: 'strick 2/11/2006 00:12'! shutdownButton ^self buttonNamed: 'SHUTDOWN ALL!!' action: #doShutdown color: self buttonColor help: 'Forcibly shutdown all desktops on the Xrfb Desktop Server, dropping any connections from clients'! ! !XrfbServerConfigMorph methodsFor: 'as yet unclassified' stamp: 'strick 2/11/2006 01:14'! cancelButton ^self buttonNamed: 'Cancel' action: #delete color: self buttonColor help: 'Don''t make changes'! ! !XrfbServerConfigMorph methodsFor: 'as yet unclassified' stamp: 'strick 2/11/2006 01:48'! doOK | p | self maker httpServerHostAddress: hostBox contents asString. p := portBox contents asString asInteger. p ifNotNil: [ self maker httpServerPortNumber: p]. self maker httpServerPassword: passwordBox contents asString. self delete. ! ! !XrfbServerConfigMorph methodsFor: 'as yet unclassified' stamp: 'strick 2/11/2006 01:13'! okButton ^self buttonNamed: 'OK' action: #doOK color: self buttonColor help: 'Accept Changes'! ! !XrfbServerConfigMorph methodsFor: 'as yet unclassified' stamp: 'strick 2/11/2006 01:36'! rebuild: ignoredText | label | label := ' Configure the Xrfb Desktop Server'. self removeAllMorphs. self addARow: { (StringMorph contents: label ) lock }. self addARow: { (StringMorph contents: 'Server Hostname or IP Address:' font: self myFont) lock. }. (self addARow: { (hostBox _ TextMorph new beAllFont: self myFont; crAction: (MessageSend receiver: self selector: #doOK); extent: 236@20; contentsWrapped: self maker httpServerHostAddress; setBalloonText: 'Enter the hostname or IP address of the Xrfb Desktop Server' ). }) color: Color white; borderColor: Color black; borderWidth: 1. self addARow: { (StringMorph contents: 'TCP Port Number: (default is 5899)' font: self myFont) lock. }. (self addARow: { (portBox _ TextMorph new beAllFont: self myFont; crAction: (MessageSend receiver: self selector: #doOK); extent: 236@20; contentsWrapped: self maker httpServerPortNumber asString; setBalloonText: 'Enter the TCP port of the Xrfb Desktop Server (usually 5899)' ). }) color: Color white; borderColor: Color black; borderWidth: 1. self addARow: { (StringMorph contents: 'Password:' font: self myFont) lock. }. (self addARow: { (passwordBox _ TextMorph new beAllFont: (StrikeFont passwordFontSize: 16); crAction: (MessageSend receiver: self selector: #doOK); extent: 236@20; contentsWrapped: self maker httpServerPassword; setBalloonText: 'Enter the shared password for the server' ). }) color: Color white; borderColor: Color black; borderWidth: 1. self addARow: { self okButton. self cancelButton. }. ! ! !XrfbServerMaker methodsFor: 'as yet unclassified' stamp: 'strick 2/14/2006 22:45'! desktopForUrl: url objectId: objectId | pageExtent morph client hostAddr hostPort socket displayNumber msg textMorph | "strick TODO: a better way than hardwiring 800x600" pageExtent := 800@600. outerMorph _ TXrfbMorphic new initializeOpaque: true extent: pageExtent; yourself. teaWorld _ TMorphMonitor new initializeWithWorld: nil extent: pageExtent; yourself. teaWorld eventsTo: outerMorph. world := teaWorld world. Transcript cr; show: 'makeRFBViewer: ', url, ' objectId: ', objectId. [ displayNumber := XrfbServerMaker new rpcLaunchServerForDocumentUrl: url objectId: objectId. ] on: Error do: [ :ex | "Could not talk to Xrfb Server -- bad host, port, password, or it is not running -- or error fetching URL" textMorph := TextMorph new beAllFont: ((TextStyle named: #ComicBold) fontOfSize: 32); backgroundColor: Color yellow; crAction: (MessageSend receiver: self selector: #yourself); extent: pageExtent; contentsWrapped: url, ' ================================== ',ex messageText asText; setBalloonText: 'This decribes the error received when trying to connect to the Xrfb Desktop Server'; centered; yourself. world addMorph: textMorph. ^ outerMorph ]. [ hostAddr := NetNameResolver addressForName: XrfbServerMaker new httpServerHostAddress timeout: 8. hostPort := 5900 + displayNumber. socket _ RFBClientSocket newTCP. socket connectTo: hostAddr port: hostPort. client := XrfbClient new setPassword: XrfbServerMaker new httpServerPassword; yourself. client target: teaWorld. "????" outerMorph xrfbClient: client. "<-----" morph := client asMorph. morph beSticky. world addMorph: morph. ] on: Error do: [ :ex | msg := url, ' ================================== An error occurred while trying to connect to the Xvnc desktop at host ', hostAddr asString, ' port ', hostPort asString, ' --------------- ', ex asString, ' --------------- ', ex messageText. textMorph := TextMorph new beAllFont: ((TextStyle named: #ComicBold) fontOfSize: 32); backgroundColor: Color yellow; crAction: (MessageSend receiver: self selector: #yourself); extent: pageExtent; contentsWrapped: msg asText; setBalloonText: 'This decribes the error received when trying to connect to the Xrfb Desktop Server'; centered; yourself. world addMorph: textMorph. ^ outerMorph ]. client connectTo: socket. client isConnected ifFalse:[ " a little cleanup " socket ifNotNil:[socket destroy]. morph delete. msg := url, ' ================================== RFBClient failed to connect.'. textMorph := TextMorph new beAllFont: ((TextStyle named: #ComicBold) fontOfSize: 32); backgroundColor: Color yellow; crAction: (MessageSend receiver: self selector: #yourself); extent: pageExtent; contentsWrapped: msg asText; setBalloonText: 'This decribes the error received when trying to connect to the Xrfb Desktop Server'; centered; yourself. world addMorph: textMorph. ^ outerMorph ]. morph bounds: world bounds. morph visible: true. ^outerMorph. ! ! !XrfbServerMaker methodsFor: 'as yet unclassified' stamp: 'strick 2/11/2006 03:56'! doSomething: aVerb | request reply answers infos errors text | request := '/', aVerb. reply := self httpGetWithCommand: request. [ answers := self getDataFromXml: reply withTag: #answer. infos := self getDataFromXml: reply withTag: #info. errors := self getDataFromXml: reply withTag: #error. ] on: XrfbHttpReplyException do: [ :ex | errors := { ex messageText } ]. text := 'ANSWERS: ', answers asString, ' INFOS: ', infos asString, ' ERRORS: ', errors asString. text := ''. answers ifNotNil: [ answers do: [ :x | text := text, x asString, String cr ] ]. infos ifNotNil: [ infos do: [ :x | text := text, '-- ', x asString, String cr ] ]. errors ifNotNil: [ errors do: [ :x | text := text, '***ERROR*** ', x asString, String cr ] ]. ( text = '' ) ifTrue: [ text = 'OKAY.' ]. ^ text. ! ! !XrfbServerMaker methodsFor: 'as yet unclassified' stamp: 'strick 1/12/2006 01:02'! getAnswersFromXml: xml | errorList answerList | "first, look for any #error tags" errorList := self getDataFromXml: xml withTag: #error. "if there are any, report them as error" (errorList size > 0) ifTrue: [ self error: (errorList inject: 'request failed' into: [ :z :e | z, ' : ', e ]) ]. "otherwise, look for answers" answerList := self getDataFromXml: xml withTag: #answer. "should always be at least one" (answerList size < 1) ifTrue: [ self error: 'request failed: no answers' ]. "return the list of answers. (if you expect only one, take first)" ^ answerList ! ! !XrfbServerMaker methodsFor: 'as yet unclassified' stamp: 'strick 2/11/2006 02:49'! getDataFromXml: someXml withTag: aTag | z document | z := OrderedCollection new. [ document := XMLDOMParser parseDocumentFrom: (ReadStream on: someXml). ] on: SAXException do: [ :ex | XrfbHttpReplyException new signal: 'An error occurred while trying to contact the Xrfb Desktop Server. ------------------------------------------ Most likely, either the Xrfb server is not running, it is not reachable due to a firewall, or the Host, Port, or Password is not configured correctly. (Use the menu "Tools" >> "Xrfb Desktops Control Panel" >> "Configure" ) ------------------------------------------- ', someXml. ^ nil ]. document tagsNamed: aTag do: [:x | z add: x characterData ]. ^ z! ! !XrfbServerMaker methodsFor: 'as yet unclassified' stamp: 'strick 1/10/2006 20:44'! httpGetWithAbsoluteUrl: anAbsoluteUrl " self new httpGetWithAbsoluteUrl: 'http://google.com/' " ^ (Url absoluteFromText: anAbsoluteUrl) retrieveContents content! ! !XrfbServerMaker methodsFor: 'as yet unclassified' stamp: 'strick 2/6/2006 23:51'! httpGetWithCommand: aCommand "aCommand is of form /verb?query=stuff self new httpGetWithCommand: '/echo?wish= " | absoluteUrl | aCommand first = $/ ifFalse: [ self error: 'aCommand should begin with /' ]. absoluteUrl := 'http://', self httpServerHostAddress, ':', self httpServerPortNumber asString, '/', self httpServerPassword, aCommand. ^ self httpGetWithAbsoluteUrl: absoluteUrl ! ! !XrfbServerMaker methodsFor: 'as yet unclassified' stamp: 'strick 2/11/2006 01:19'! httpServerHostAddress ^ HttpServerHostAddress ifNil: [ '127.0.0.1' ]! ! !XrfbServerMaker methodsFor: 'as yet unclassified' stamp: 'strick 2/11/2006 01:17'! httpServerHostAddress: newAddress HttpServerHostAddress := newAddress! ! !XrfbServerMaker methodsFor: 'as yet unclassified' stamp: 'strick 2/11/2006 01:20'! httpServerPassword ^ HttpServerPassword ifNil: [ 'zzzzzz' ]! ! !XrfbServerMaker methodsFor: 'as yet unclassified' stamp: 'strick 2/11/2006 01:17'! httpServerPassword: newPassword HttpServerPassword := newPassword ! ! !XrfbServerMaker methodsFor: 'as yet unclassified' stamp: 'strick 2/11/2006 01:21'! httpServerPortNumber ^ HttpServerPortNumber ifNil: [ 5899 ] ! ! !XrfbServerMaker methodsFor: 'as yet unclassified' stamp: 'strick 2/11/2006 01:18'! httpServerPortNumber: newPortNumber HttpServerPortNumber := newPortNumber! ! !XrfbServerMaker methodsFor: 'as yet unclassified' stamp: 'strick 2/10/2006 23:15'! rpcCleanServers | request reply | request := '/clean'. reply := self httpGetWithCommand: request. ^ self getAnswersFromXml: reply! ! !XrfbServerMaker methodsFor: 'as yet unclassified' stamp: 'strick 2/6/2006 23:28'! rpcEcho: stuff "A service of the RfbServerServer, for testing upness " | request reply | "The /echo verb expects one query thing named 'what' " request := '/echo?what=', stuff encodeForHTTP. reply := self httpGetWithCommand: request. "expect only one answer, to take the first" ^ (self getAnswersFromXml: reply) first! ! !XrfbServerMaker methodsFor: 'as yet unclassified' stamp: 'strick 2/6/2006 23:28'! rpcLaunchServerForDocumentUrl: aDocumentUrl objectId: anId | request reply | request := '/launch?id=', anId encodeForHTTP, '&url=', aDocumentUrl encodeForHTTP. reply := self httpGetWithCommand: request. ^ (self getAnswersFromXml: reply) first! ! !XrfbServerMaker methodsFor: 'as yet unclassified' stamp: 'strick 2/6/2006 23:28'! rpcListServers | request reply | request := '/list'. reply := self httpGetWithCommand: request. ^ self getAnswersFromXml: reply! ! !XrfbServerMaker methodsFor: 'as yet unclassified' stamp: 'strick 2/6/2006 23:28'! rpcShutdownAllServers | request reply | request := '/shutdown'. reply := self httpGetWithCommand: request. self getAnswersFromXml: reply! ! !XrfbServerMakerTests methodsFor: 'as yet unclassified' stamp: 'strick 1/12/2006 19:09'! testBogus " self new testBogus " " self should: [ RfbServerMaker new httpGetWithPath: '/bogus' ] raise: Exception. " " | gotError | gotError := false. [ RfbServerMaker new httpGetWithPath: '/bogus' ] on: Exception do: [ gotError := true ]. self assert: gotError. " "*********** I don't know how to write this negativd test *************" ^ #ok! ! !XrfbServerMakerTests methodsFor: 'as yet unclassified' stamp: 'strick 2/2/2006 23:36'! testEcho " self new testEcho " | answer x | "The /echo verb should echo back the query stuff in some manner." x := 'pb&j?=peanutButter+Jelly', (Character value: 1) asString, (Character value: 250) asString. answer := XrfbServerMaker new rpcEcho: x. self assert: ( x = answer ). ^ #ok! ! !XrfbServerMakerTests methodsFor: 'as yet unclassified' stamp: 'strick 2/6/2006 23:30'! testLaunch " self new testLaunch " | answer x | x := 'http://www.google.com'. answer := XrfbServerMaker new rpcLaunchServerForDocumentUrl: x objectId: 'testId'. self assert: answer isAllDigits. ^ #ok! ! !XrfbServerMakerTests methodsFor: 'as yet unclassified' stamp: 'strick 2/7/2006 16:39'! testSetPassword self assert: 'abcdefg' = ( XrfbClient new setPassword: 'abc','defg'; requestPassword: 'sample label for user' ) ! ! VDesktopTablet removeSelector: #modelUrl:! VDesktopTablet removeSelector: #url:!