• Updated 2023-07-12: Hello, Guest! Welcome back, and be sure to check out this follow-up post about our outage a week or so ago.

Experimental VNC (Remote Desktop) Server for the Classic Macintosh

LaPorta

Well-known member
Ok I totally missed that this was running on a new PC…my fault. I thought it was old Mac to old Mac! My wish list would then include allowing Screen Sharing on macOS to be able to do it :p
 

marciot

Well-known member
@LaPorta: It wouldn't be hard to make a VNC client for the Mac Plus that was compatible with the VNC server. However, step 1 is to make it reliable enough for day-to-day use.
 

Crutch

Well-known member
This is very nice! Just to be clear, is remote control possible or is this a view-only thing? I would be very interested in how you implemented remote control, if indeed you did .. I am thinking one would use a jGNEFilter or the journaling mechanism. In my mental image of coding this up probably not super hard (no doubt tons of subtle details though) but would be really cool to play with.
You also won't see the cursor in the remote client. This is because on color Macs I am asking Mac OS to copy a portion of the screen to an offscreen B&W buffer that simulates a B&W display and MacOS will not copy the cursor.

There is a way to copy the cursor! Have you ever noticed you can get a cursor in the image with the Cmd-Shift-3 FKEY? This link includes the asm code to do it. I used a similar trick in my Exposé cdev to keep the cursor visible but prevent it from getting trashed by moving around while I blit zoom rects directly to screen memory:


The short version is you need to futz with a couple lomem globals so CopyBits thinks the cursor is already hidden. Here's what the implementor had to say about this trick:

; Now we need to do a sick thing. This is to make the cursor visible in the pict, once it ; is saved off. The problem is that CopyBits will ShieldCursor in order to make it not ; appear in an offscreen picture. We need it there in order to make it a real screen ; snapshot. We will mark the cursor as Invisible so that QD will think that it doesn't ; have to ShieldCursor. We do this by setting it invisible, and decrementing the CrsrState. ; We also want to freeze the mouse while this goes on, since we don't want the mouse to ; change around during the save. It takes long enough that we can get multiple smashed ; copies of the cursor. The mouse is frozen by the CrsrState change.

I also bet you could speed everything up on color Macs by skipping the offscreen B&W buffer blit and just doing the color-to-B&W conversion yourself pixel-by-pixel in a tight loop before streaming out the bitmap over TCP.

Anyway, nicely done!
 

marciot

Well-known member
This is very nice! Just to be clear, is remote control possible or is this a view-only thing? I would be very interested in how you implemented remote control, if indeed you did .. I am thinking one would use a jGNEFilter or the journaling mechanism. In my mental image of coding this up probably not super hard (no doubt tons of subtle details though) but would be really cool to play with.

It seems easy, doesn't it? I thought so too, but it actually took me three months to get the remote control to work. Moving the mouse is relatively straightforward, it involves writing some low-memory global. Clicking is easy too -- just post an event. But click and drag -- that consumed three months of my life!

I actually wrote a Journaling driver. It almost worked -- just well enough to consume another month of my life chasing a false lead. In the end, I concluded the journaling mechanism was borked when Apple introduced multitasking in System 7.

Things like jGNEFilters and patching traps don't work either, because the dragging routines don't rely on events, they rely on low-memory button state global which on the Mac Plus is continually overwritten by the VIA interrupt. Writing a button state to that low-memory variable works on later Macs, and is in fact what ChromiVNC does, but it does not work on the Mac Plus -- therein was the problem.

In the end, the solution was easy but non-obvious and I only came across it by carefully examining the disassembled code for the VIA interrupt, but this was after I had tried all the other hard things that did not work.

There is a way to copy the cursor! Have you ever noticed you can get a cursor in the image with the Cmd-Shift-3 FKEY? This link includes the asm code to do it. I used a similar trick in my Exposé cdev to keep the cursor visible but prevent it from getting trashed by moving around while I blit zoom rects directly to screen memory:

I'll have to look into this. Thanks for the tip!

I also bet you could speed everything up on color Macs by skipping the offscreen B&W buffer blit and just doing the color-to-B&W conversion yourself pixel-by-pixel in a tight loop before streaming out the bitmap over TCP.

Possibly, but on the 68000 bit-shifts are expensive, taking an instruction cycle per shift and each instruction requires a memory access on the 68000, which further slows things down. So I don't think color conversion is practical on the 68000, but it may be practical on the 68020 with an instruction cache. I guess I am not ruling out a color version of the VNC server, but I do think there are challenges.

Anyway, nicely done!

Thanks!
 

marciot

Well-known member
Does anyone know whether all 68k Macs support 2bpp, 4bpp and 8bbp color modes? I know some later Macs only supports the true color modes. And do all color Macs support 512x384 and 640x480?

I could probably try supporting something like 2bbp at 512x384, then maybe work my way up to 4bbp.
 

Crutch

Well-known member
Fascinating, thanks for this explanation. I would love to hear how you solved it.

This sounds familiar now about mouse drags. (To implement the hide-windows-while-dragging feature in Exposé I had to identify a weird trap to patch because GetNextEvent doesn't get called while dragging and the Drag Manager doesn't have a nice DragHook like the old _DragGrayRgn trap; I ended up patching _XorRgn because that repeatedly gets called to update the gray icon outline during Finder drags!) Even though GetNextEvent doesn't get called, I would have assumed StillDown or Button or WaitMouseUp are used and you could patch those for exhaustive coverage. But the Drag Manager just checks the LM global for the mouse button? Weird.

Possibly, but on the 68000 bit-shifts are expensive, taking an instruction cycle per shift and each instruction requires a memory access on the 68000, which further slows things down. So I don't think color conversion is practical on the 68000, but it may be practical on the 68020 with an instruction cache. I guess I am not ruling out a color version of the VNC server, but I do think there are challenges.

Understood, but a BSET is much faster so I'm not sure why you would have to use shift instructions. I may be misunderstanding your exact use case though. (if a shift is really necessary you could also skip it by special-casing 8 times, or 16 if you also want to avoid a SWAP ... but I'm sure you've thought of all this already!)

Edit: on the 68000 proper, of course, you don't have to worry about color because there is no 68000-based Mac that supports color. You will always have an '020 or better on a color Mac.
 

marciot

Well-known member
Fascinating, thanks for this explanation. I would love to hear how you solved it.

This sounds familiar now about mouse drags. (To implement the hide-windows-while-dragging feature in Exposé I had to identify a weird trap to patch because GetNextEvent doesn't get called while dragging and the Drag Manager doesn't have a nice DragHook like the old _DragGrayRgn trap; I ended up patching _XorRgn because that repeatedly gets called to update the gray icon outline during Finder drags!) Even though GetNextEvent doesn't get called, I would have assumed StillDown or Button or WaitMouseUp are used and you could patch those for exhaustive coverage. But the Drag Manager just checks the LM global for the mouse button? Weird.

I tried patching Button, and that did not work -- but I did not try patching StillDown and WaitMouseUp, as I was not aware of those (are they available on the Plus?).

So it turns out the VIA interrupt deglitches the mouse button by waiting three ticks before updating the MBState global. It keeps track of this by the MBTicks global. I found out that by setting this to the future (ahead of TickCount) effectively caused the VIA interrupt to wait indefinitely, which allowed me to manipulate the MBState global to get dragging and menu selection to work. I'm not sure why this trick isn't necessary on later Macs, but I assume the newer Via interrupt only writes to MBState when there is an actual change in the mouse button, while on the Mac Plus it happens continuously (except during the deglitching phase).

Understood, but a BSET is much faster so I'm not sure why you would have to use shift instructions. I may be misunderstanding your exact use case though. (if a shift is really necessary you could also skip it by special-casing 8 times, or 16 if you also want to avoid a SWAP ... but I'm sure you've thought of all this already!)

Edit: on the 68000 proper, of course, you don't have to worry about color because there is no 68000-based Mac that supports color. You will always have an '020 or better on a color Mac.

I didn't think about it too hard, since I did not originally intend to support color. The crux of the problem is that in the VNC protocol, it is the client that tells the VNC server how to pack color bits. So I assume this would involve several shifts and masking operations if the color packing was incompatible (which it likely will be, because the server has to respect the wishes of the client).

A fully-compliant VNC server must always support raw encoding, which has this color conversion problem. In mine, I cheat by only supporting the TRLE encoding. The TRLE encoding has a 1, 2 and 4 bpp palleted mode, which in the case of the Mac Plus, allows me to transmit packed B&W pixels without any color conversion or byte expansion. I might be able to leverage this trick for 1, 2 and 4 bpp modes on Color Macs as well, but it cannot be used for 8 bit or true color modes.
 

MacKilRoy

Well-known member
How would performance and compatibility and usefulness compare to running something like Timbuktu?
 

marciot

Well-known member
@MacKillRoy: I've never used Timbuktu. It appears that it requires System 8.x at least, so it will not perform on a Mac Plus for sure!
 

marciot

Well-known member
@MacKilRoy: Nevermind, I see Timbuktu 1.0 works on 68k, so it would be an interesting comparison. Thanks for sharing. Is there a Windows PC client for it? Has the protocol been documented anywhere?
 

MacKilRoy

Well-known member
@MacKilRoy: Nevermind, I see Timbuktu 1.0 works on 68k, so it would be an interesting comparison. Thanks for sharing. Is there a Windows PC client for it? Has the protocol been documented anywhere?
There was a windows and a Mac version back in the day. It was used to remotely connect to a computer kind of like how Apple Remote Desktop was used. I believe the software was interchangeable, meaning it could connect to and from either Mac or Windows, with either Mac or Windows.
 

marciot

Well-known member
There was a windows and a Mac version back in the day. It was used to remotely connect to a computer kind of like how Apple Remote Desktop was used. I believe the software was interchangeable, meaning it could connect to and from either Mac or Windows, with either Mac or Windows.
It would be pretty amazing if someone could track down the protocol specification. The VNC protocol isn't exactly optimized for low-color or low-performance devices, so I would imagine Timbuktu would perform better than my solution -- after all they've had years to perfect it. But the only way to know for sure would be to run some benchmarks.
 

marciot

Well-known member
@MacKilRoy: VNC uses TCP, while Timbuktu uses UDP. So, big win there.

Eventually, it may make sense to abandon VNC compatibility and simply come up with a new protocol. The biggest problem is having clients for various platforms, but I think maybe one solution is to implement a WebSockets capable web server under MacTCP and then implement the client in JavaScript for modern PCs. Although WebSockets also uses TCP -- so that's a negative.
 

Crutch

Well-known member
This is fascinating stuff. Thanks for sharing all this!
I tried patching Button, and that did not work -- but I did not try patching StillDown and WaitMouseUp, as I was not aware of those (are they available on the Plus?).
or 1, 2 and 4 bpp modes on Color Macs as well, but it cannot be used for 8 bit or true color modes.
Yes, StillDown and WaitMouseUp existed since the original 1984 ROMs. However in looking at the ROM I see that WaitMouseUp calls StillDown which calls Button, and DragGrayRgn calls WaitMouseUp ... so my suggestion was worthless, if you patch Button you should have all these routines covered including any drag that is using DragGrayRgn and not the Drag Manager.

So I am pretty sure you are bumping into what I noticed before about the Drag Manager .... in my vague memory it does something funky like checking for ADB and, if present, using an ADB call to get the mouse button state. Why? I have no idea! But it explains what you observed.

So, if running System 6 or earlier, I wonder if just patching Button would actually work? Looks like you're running Sys 7 though, in which case maybe you could patch whatever ADB call is being used if you wanted to avoid worrying about the VIA. However in any event, your solution is fascinating and clever, so hat's off to you!
 

cheesestraws

Well-known member
I found out that by setting this to the future (ahead of TickCount) effectively caused the VIA interrupt to wait indefinitely, which allowed me to manipulate the MBState global to get dragging and menu selection to work

That's very clever and made me wince inwardly. Well done. :D
 

marciot

Well-known member
@Crutch and @cheesestraws since you guys seem to be enjoying my deeds of Mac derring-do, let me tell you about my ordeal with the Journaling driver, for therein lies a tale...

So while I was pulling my hair out over the MBState issue, I came across those two pages in the ancient scriptures (Inside Macintosh) about the Journaling driver and the fabled desk accessory Apple engineers had used to stress test the Mac UI. Surely therein lay the solution to my problem. So I whipped up a desk accessory and lo and behold, it worked... for about two seconds... sometimes four seconds, maybe five if I got lucky.

So the idea with the journaling driver is pretty simple. You write the reference ID of your driver to journalRef, and you set journalFlag to whether you want it to record or playback. Not too difficult, pretty self-explanatory. But I found out that whenever journalRef was set, it would get unset some time random interval later, like those weird little boxes that turns itself off. I was able to declare a short-lived victory by resetting the journalRef each time my desk accessory got idle time, via the accRun, which allowed me to collect and playback events for longer periods under System 8.0 (on Basillisk II), but this same technique crashed the Mac Plus under System 7.0, so I had to abandon it. This left me trying to understand why journalRef was being reset and by whom.

Wouldn't it be nice if I could simply watch a memory location for changes? AFAIK, no debugger can actually do this while the system is running at full speed and stepping was not an option since this was only happening every several seconds. So I decided to grab a copy of the PCE emulator and hacked it to print out the program counter whenever the journalRef address was accessed. This worked well and I hacked it to print out all the program counter values leading up to the change, so I could trace the chain of instructions that led to the fault. To make a long story short, the Button trap will do a Control call whenever journalRef is set, the Control call then checks the device unit table to see if there is an entry for the driver. If the entry exists, it calls the driver; if it does not, it returns an error code, which causes the Button trap to clear the journalRef -- a perfectly valid thing to do, I suppose -- BUT WTF IS MY DRIVER NOT IN THE DRIVER UNIT TABLE?

Well, I again hacked PCE to show me whenever the driver unit table was written to and I found it out it was done very frequently. My hacked PCE also told me the program counter when it was happening, but it was in the middle of RAM somewhere and I wasn't really able to gleam anything from the surrounding instructions to tell me what the code was doing, but the fact it was done periodically made me fairly certain it had something to do with task switching. Back in the days, desk accessories were legitimate device drivers that could be expected to hang around while an application was running, but now a days System 7.0 treats them as mini apps, so my guess is that this involves removing them from the driver unit table at each context switch. If this happens to coincide with an event that needs to be sent to the journaling driver, then the Control call fails and journalRef gets reset to zero. Not good.

Anyhow, I tried a couple things to attempt to make the Journaling driver to not act as a desk accessory, by loading it into the SystemHeap or putting a dot in the driver name so it wouldn't be a desk accessory, but none of that really helped. Maybe if I had loaded it at system startup via an INIT it would have worked. But anyhow, at this point I already had found out the journaling mechanism was kind of slow so it didn't seem like a workable solution for the VNC server anyway, so I kind of gave up on the idea.

Oh, one last thing. I also extracted Apple's own journaling Desk Accessory from the Guided Tour disk. It seemed to crash pretty spectacularly on modern systems. I did disassemble it to see if it held any secrets, that's how I learned about crsrCouple, but otherwise it seemed to be pretty much structured like my own Desk Accessory, so I don't think my attempt was wrong in any way.
 
Last edited:

Crutch

Well-known member
I thought about this for a while and decided that it’s pretty weird.

It looks like ALL drivers in the unit table that aren’t DAs running in the DA Handler (and even those, maybe? It’s a little hard to tell) are switched out of the unit table on a process switch. I’m not exactly sure why. They still get time from the Process Manager (which seems to check a separate table of switched-out drivers to give time to, as needed), so why take them out of the unit table?

Indeed the Toolbox Event routines make a _Control call to the journaling driver, which will return an error if the requisite driver isn’t in the unit table, which (as you say) results in the Toolbox Event routines’ turning off journaling.

I don’t think installing your DRVR from an INIT into the system heap will help, because (1) anything in the unit table seems to get switched out on a process switch, and (2) the journaling mechanism seems to require that your DRVR live in the unit table! This seems to mean cross-app journaling is impossible in System 7 without patching a ton of traps. I wonder if I am missing something. (I did generally have the sense that the journaling mechanism was sort of deprecated circa 1985, after the original Guided Tours went out of style …. )
 

eharmon

Well-known member
As a test report, just tried this on my Mac SE with a 40MHz accelerator and a Dayna Pocket SCSI/Link this is actually....usably quick!

Nothing lightning speed but it's certainly tolerable for navigating the file system, running stuffit, etc. This is really awesome!
 

Attachments

  • maxie_vnc2.mov
    8.1 MB

marciot

Well-known member
As a test report, just tried this on my Mac SE with a 40MHz accelerator and a Dayna Pocket SCSI/Link this is actually....usably quick!

Nothing lightning speed but it's certainly tolerable for navigating the file system, running stuffit, etc. This is really awesome!
That looks great! Does it ever hang the system for you? I have a theory that on the Mac Plus MacTCP is freezes the computer occasionally simply because it can't keep up and likely has some sort of race condition somewhere related to that. It would be nice if it were more stable on faster systems.
 

marciot

Well-known member
(I did generally have the sense that the journaling mechanism was sort of deprecated circa 1985, after the original Guided Tours went out of style …. )

I did come across another reference to the Journaling mechanism in tech note 1104, recently, which pretty much confirms this:

"TickCount and GetKeys are not interrupt safe. This is because they support the Journaling Mechanism, as described in Inside Macintosh I , page 261. While the Journaling Mechanism is long obsolete -- leaving the core implementation of these routines interrupt safe -- it is legal for third party extensions to patch these routines with non-interrupt safe patches."

Still, I find it to be annoying Apple killed it off without a good alternative, although I suspect it wasn't intentional. I think we need to write a proper obituary for it and give it a proper send off, because nobody at Apple bothered to do so.

R.I.P Journaling Mechanism.
 
Top