• 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.

Making a VNC server for A/UX

cheesestraws

Well-known member
I think I might give up on getting X working for now. I've gone through the traditional 5 stages of trying to get X11 software working, and have gone from anger to acceptance.

Input support in A/UX continues to be sort of hilarious to take to bits. I think what's going on is: The kernel does substantial mucking about with the keyboard, and so the keyboard X sees doesn't really line up with the ADB keyboard. Things are then made even more complicated, because the mouse is managed by the kernel inserting escape sequences into that emulated keyboard stream, and those escape sequences consist basically of the raw data received from the mouse over ADB. (Abstraction? What's that?)

So, if I were to step totally outside the X server, I can only get relative cursor movement at best, and the button is ropy for reasons I still don't properly understand.

A better option would be to talk directly to X, the way I do to the mac environment, and insert events. Using XWarpPointer to move the mouse pointer works, more or less. Mouse buttons are more complicated: I can do the mouse buttons through the kernel support which is, as I noted above, ropy (though probably fixable), and slightly mad if I'm doing the pointer movement through XWarpPointer. What I should do, though, is use X events to send mouse down and mouse up. Except that while twm is initially letting you position a window, it seems to wedge all events: so you end up in the same ludicrous position we were in the mac world, where sending a synthesised mouse down event means that you can't send a subsequent synthesised mouse up event. In the Mac land, this is kind of forgiveable: in the X world, it's really silly. Key events are being even more pain to get working. XGetInputFocus returns window 1 no matter what I do, and I don't know how to fix it.

In other words, I'm reliving the reasons why the XTEST extension works. Annoyingly, given that the copyright on the XTEST specification shows that it was initially designed by UniSoft, there's no XTEST or proto-XTEST implementation in A/UX as far as I can see.

The final nail in this coffin was finding out that Xlib thinks it has carte blanche to terminate your process when your X server goes away. Which we don't want to happen here.

Making all this work is a lot of work given that:
  1. no bugger uses X11 on A/UX
  2. I don't want to use X11 on A/UX and
  3. I don't like writing software for X11.
and isn't guaranteed to ever work properly.

So I'm proposing, unless anyone strenuously objects, to abandon X sessions entirely for the moment, and just make console and mac sessions work properly if I can.
 

tecneeq

Well-known member
I'll allow it, my dude. 🥴

If i remember this right, you have two kinds of session
  1. One is X11 (10R3 based on the MIT release 10R3 and 11R3?) in which you can run any X11 client you want to, but you can not run MacOS applications. Your first X client would be twm or some other window manager.
  2. The second is called mac32, in which you can run MacOS 7.0 applications, provided they don't do special tricks, like skipping the regular APIs. You can run X11 clients in MacX, can you not? You could run xnest and then twm in a windows in mac32?
I wanted to install A/UX this weekend, but have to get this working and out of the way first:
PXL_20221207_150442058.jpg
BTW, I highly recommend those 40€ Ikea folding tables, you can pop one or two up in the living room and say it's a temporary project. The foldability of the table underlines the temporary nature of your space occupation and is a reminder to finish your damn projects. 😅
 

cheesestraws

Well-known member
The second is called mac32, in which you can run MacOS 7.0 applications, provided they don't do special tricks, like skipping the regular APIs. You can run X11 clients in MacX, can you not? You could run xnest and then twm in a windows in mac32?

I'm not sure what the official name of this is, I call it 'mac32' because that's the name of the script that starts it :). But yes, you could still run X applications in MacX under the mac environment. It's purely the full-screen "X directly controls the screen etc" sessions that won't work properly.
 

cheesestraws

Well-known member
Keyboards are awful. VNC makes things worse because it uses X keysyms, which aren't sure whether they're key codes or a character encoding or something betwixt the two somewhere.

The keyboard driver in A/UX can be in several modes, defined in sys/key.h:

C:
#define KEY_ASCII    0    /* ascii mode ... with timeouts */
#define KEY_RAW        1    /* raw key codes ... when something changes */
#define KEY_ARAW    2    /* raw key codes ... with KEY_ASCII interface */
#define KEY_MAC        3    /* raw keycodes -> keyTrans -> ascii */

Despite KEY_MAC being a thing, the UI environment actually seems to use KEY_ARAW, where raw ADB physical key codes are passed on. This made my heart sink a little, because it means we somehow need to reverse engineer the physical key code from a keysym.

But what's it passing it on to?

Each kernel subsystem that wants to take control of the keyboard essentially installs a callback function by calling a function called key_op. One of the things this function does is to take a new callback and return the old one.

This is the first piece of good news: we can slightly abuse this to get the current callback that's receiving key presses, which gives us a nice place to inject those keypresses. Because we can't get the callback directly, we have to do a bit of a shuffle. First, we disable interrupts to stop the computer noticing what we're about to do. Then we swap in an invalid callback (which returns the real one). Then we swap the real one back in and re-enable interrupts, but keeping our reference to the callback. We can then call that callback to inject a keypress. We do this dance for every keypress, because the callback may have changed since the last keypress (it depends on what kind of session we're in at the time).

But that still doesn't help much with getting the key codes for keysyms: how are we to know what to put into that callback? At the moment, it's heavily bodged, but I noticed something interesting tonight while reading through the UI keyboard callback: the thing that posts key up and key down events in the mac environment and updates low memory is in the kernel, not in the Mac System. And the kmap/KCHR that are currently in use—the first to map from physical to virtual keycodes, and the latter to map from virtual keycodes to characters—are both stored in the kernel. With sensible defaults.

I had known that these were there, because I saw them in the kernel export list, and also because they're mentioned in key.h, but I thought these were only used for KEY_MAC mode. But they're not: the UI keyboard callback uses them to generate the character codes to put into the events.

This is extremely handy, because it means that in theory for non-modifiers we can just come up with a mapping of X keysyms to MacRoman characters, then use the kmap and KCHR from the kernel to construct reverse lookup tables to go from "what character do we want to insert" to "what physical ADB keycodes should we generate".

This is easier said than done, but it's also probably easier than manually trying to curate huge tables of what keys go where. Life is too short for that, and if I'm going to do something time consuming I'd rather it be interesting :).

Any thoughts?
 

Phipli

Well-known member
Keyboards are awful. VNC makes things worse because it uses X keysyms, which aren't sure whether they're key codes or a character encoding or something betwixt the two somewhere.

The keyboard driver in A/UX can be in several modes, defined in sys/key.h:

C:
#define KEY_ASCII    0    /* ascii mode ... with timeouts */
#define KEY_RAW        1    /* raw key codes ... when something changes */
#define KEY_ARAW    2    /* raw key codes ... with KEY_ASCII interface */
#define KEY_MAC        3    /* raw keycodes -> keyTrans -> ascii */

Despite KEY_MAC being a thing, the UI environment actually seems to use KEY_ARAW, where raw ADB physical key codes are passed on. This made my heart sink a little, because it means we somehow need to reverse engineer the physical key code from a keysym.

But what's it passing it on to?

Each kernel subsystem that wants to take control of the keyboard essentially installs a callback function by calling a function called key_op. One of the things this function does is to take a new callback and return the old one.

This is the first piece of good news: we can slightly abuse this to get the current callback that's receiving key presses, which gives us a nice place to inject those keypresses. Because we can't get the callback directly, we have to do a bit of a shuffle. First, we disable interrupts to stop the computer noticing what we're about to do. Then we swap in an invalid callback (which returns the real one). Then we swap the real one back in and re-enable interrupts, but keeping our reference to the callback. We can then call that callback to inject a keypress. We do this dance for every keypress, because the callback may have changed since the last keypress (it depends on what kind of session we're in at the time).

But that still doesn't help much with getting the key codes for keysyms: how are we to know what to put into that callback? At the moment, it's heavily bodged, but I noticed something interesting tonight while reading through the UI keyboard callback: the thing that posts key up and key down events in the mac environment and updates low memory is in the kernel, not in the Mac System. And the kmap/KCHR that are currently in use—the first to map from physical to virtual keycodes, and the latter to map from virtual keycodes to characters—are both stored in the kernel. With sensible defaults.

I had known that these were there, because I saw them in the kernel export list, and also because they're mentioned in key.h, but I thought these were only used for KEY_MAC mode. But they're not: the UI keyboard callback uses them to generate the character codes to put into the events.

This is extremely handy, because it means that in theory for non-modifiers we can just come up with a mapping of X keysyms to MacRoman characters, then use the kmap and KCHR from the kernel to construct reverse lookup tables to go from "what character do we want to insert" to "what physical ADB keycodes should we generate".

This is easier said than done, but it's also probably easier than manually trying to curate huge tables of what keys go where. Life is too short for that, and if I'm going to do something time consuming I'd rather it be interesting :).

Any thoughts?
Are these the keycodes where "123" is one of the arrow keys? Or something different?

If they are, I remember there been a full map of keycodes in the RealBasic documentation. If that is handy at all.
 

foetoid

Well-known member
I'm stoked about this project. While not something I'll likely use in my environment, I will definitely be following development.

I'm one of those folks who runs a 4 post rack. I'm looking at using my NW KVM in conjunction with a Wombat to spare CPU cycles.

Not suggesting that this is not a valuable project, I am excited about it. Really just saying that I do things the expensive way...
 

timtiger

Well-known member
Really cool @cheesestraws! I‘ll be happy to test it in the future! It’s funny when you take in account that you‘re streaming a console window that you could access via ssh :)

I know, A/UX is more than the console considering its window sessions… Your niche devwork sounds like a lot of fun! Thanks for sharing so much insights 🙏
 

cheesestraws

Well-known member
I'm one of those folks who runs a 4 post rack. I'm looking at using my NW KVM in conjunction with a Wombat to spare CPU cycles.

To be fair, this is the sensible way to do it, if one has an IP KVM to use :). Mine is already spoken for, alas. And I wanted to see if it was possible to control the Mac environment from outside the Mac environment.

'm one of those folks who runs a 4 post rack.

Same! Hooray for impractical home furniture. In fact this whole project began to facilitate the pun "RA/UXmount", so you can probably infer unflattering but accurate things about my sanity from this...

It’s funny when you take in account that you‘re streaming a console window that you could access via ssh :)

There's actually a reason for this. I nearly said a practical reason, but there's no practical reason for any of this, let's face it :). The reason is: if the Mac environment crashes, or if some kinds of hybrid application prints something to standard error, then when the Mac environment exits, the standard error it's printed appears in a console window and invites you to press enter. Given how much time I spend poking the Mac environment with pointy sticks when I'm using A/UX, being able to recover from this easily is kind of handy...

I‘ll be happy to test it in the future!

Once I've got the keyboard working properly, I'll provide a binary build for anyone who wants it.
 

cheesestraws

Well-known member
More keyboard rambling.

The routine that turns keycodes into characters is called _KeyTrans. I'm working from the MacOS version of this because it's easier to read, but it looks like the A/UX version uses the same flavour of resource.

KeyTrans uses a KCHR resource, which manages a set of mappings from key code to character. In systypes.r it is defined thusly:

C-like:
type 'KCHR' {
        integer;                                                /* Version                */
        wide array [$100] {                                        /* Indexes                */
            byte;
        };
        integer = $$CountOf(TableArray);
        array TableArray {
            wide array [$80] {                                    /* ASCII characters        */
                char;
            };
        };
        integer = $$CountOf(DeadArray);
        array DeadArray {
            byte;                                                /* Table number            */
            byte;                                                /* Virtual keycode        */
            integer = $$CountOf(CompletorArray);
            wide array CompletorArray {
                char;                                            /* Completor char        */
                char;                                            /* Substituting char    */
            };
            char;                                                /* No match char        */
            char;                                                /* 16-bits for the times
                                                                   when 8 isn't enough    */
        };
};

We should be able to reverse this mapping and get information back about the key codes we need to send (rewritten by the kmap). I was a bit worried because this is a variable length resource, and we need to copy it out of kernel space into user space. However! On closer looking at the UI_keyboard code, it in fact always uses the same keytransData buffer for the KCHR:

Screenshot 2022-12-15 at 13.05.26.png

and the uinter ioctl that imports the KCHR actually copies it into this buffer. This buffer is statically sized at 3k: so we can use a similar trick to the clut and smuggle it out in chunks. (It might be better to have this as its own device node, same as the clut, but that would make the driver more complicated. Maybe a later thing). So this will be the next trick.
 

cheesestraws

Well-known member
The "let's reverse the KCHR" trick works! With a bit of mucking about, we can turn a character back into a sequence of keycodes (including dead keys!) With a bit more mucking about, and this was very tedious, we can convert X keysyms to MacRoman characters. By doing it like this, the resulting VNC server should give the right results regardless of the keyboard layout in play on the server: so people who are using non-UK keyboards won't be stuck with my defaults any more. For the moment we're still stuck in MacRoman, though.

Next, it's time to wire this code into the server...
 

cheesestraws

Well-known member
Accented characters are also working.

Screenshot 2022-12-18 at 00.38.22.png

The way this works is: when we get a keysym, we first look to see whether it's something we can get to in one keypress. If it is, we simulate that.

If not, we look at the dead keys in the KCHR resource. We index this again backwards by the "substitution character" (which is what the resource calls the resulting character). So, if we wanted an é, we'd look up é in the reverse table of dead keys. This gets us first the dead key itself, then the character that "completes" that key. That character then also has to be turned into a keypress.

When simulating this, there's a slight wrinkle: we can't trust the vnc key up event. Otherwise, we generate two key downs then two key ups, and this confuses the issue rather. So instead, if we have two keypresses, we need to make sure the first keypress is completed before the second one is done.

The nice thing about all this is that it doesn't care what keyboard layout the mac is using, even to reach characters via dead keys. If the VNC client requests LATIN CAPITAL LETTER E WITH ACUTE, it should be able to insert that character if it is reachable via the currently configured keyboard at all.
 

cheesestraws

Well-known member
The remaining "feature" that was missing, to me, was to reduce the latency on graphics updates. We were copying the contents of the framebuffer twice: once to copy it into the VNC process's address space out of the kernel (by read()ing from our framebuffer device), and once to generate the framebuffer we want to send (normaling it to 8-bit colour as we go).

The first of these turns out to be avoidable. There's a routine in the kernel called dophys(), which allows the kernel to map chunks of physical address space into a process's address space non-exclusively: so, for example, the VNC server can get the framebuffer mapped into its memory space at the same time as the mac environment also has it mapped in. The VNC server can then just copy straight out of the framebuffer into its secondary framebuffer

This works really well, has has cut off a good 100ms of interactive latency if I'm reading the numbers right, which is great. Also means less CPU thrash, because it's not just copying chunks of memory around for no reason.


View attachment Screen Recording 2022-12-20 at 13.15.40.mov
 

joshc

Well-known member
👏 amazing progress, it’s really smooth looking! Now I just need to install A/UX on something to make use of this!
 

robin-fo

Well-known member
I tried to compile the kernel module yesterday, but newconfig complained:

Bash:
aux3-shoebill.root # cd aux-minivnc
aux3-shoebill.root # cd kernel
aux3-shoebill.root # ./build.sh
aux3-shoebill.root # ./install.sh
aux3-shoebill.root # newconfig fb
newconfig: Making backup copies of /etc/inittab /etc/passwd /etc/group.
newconfig: Preparing to build a new kernel.
newconfig: Building a new kernel (this may take several minutes).

newconfig: *** PROBLEMS BUILDING A NEW KERNEL. ***
newconfig: You might try running newconfig with the -v flag.
/tmp/ncErr1223:Autoconfig error: Kernel link failed
Driver startup files in /etc/startup.d may not match kernel!
Run newunix(1m) to restore previous configuration
newconfig: Restoring saved /etc/inittab /etc/passwd /etc/group.

I tried it again with the release version, but it's the same...
 
Last edited:
Top