ULTRASONIX PORTING GUIDE Version 1.0, October 1, 1996 Keith Edwards 1.0 INTRODUCTION This guide is intended to assist people in porting the UltraSonix screenreader software. The initial development on the system was done on Sun SPARCstations running Solaris 2.5. There are a number of issues that must be addressed in porting to a new platform. To get UltraSonix usefully running on a new platform, there are several big components that must be ported: - The UltraSonix core software. - The Remote Access Protocol libraries. - The device support needed to drive attached hardware. - The runtime libraries needed by client applications. Each of these is discussed below. Additionally, there's a section titled "Future Work" that discusses what (I think) are the most important issues to be addressed once a basic port is finished. 2.0 PORTING THE ULTRASONIX CORE The core UltraSonix program is an X11R6 application implemented in C++, and has a number of dependencies on "external" software packages that are probably not found by default on the system you're porting to. 2.1 Dependencies To build the core system, you will need the following. - An ANSI C compiler (we used Sun SPARCcompilers C 3.0.1). - A C++ compiler with good support for exceptions and templates (we used Sun SPARCcompilers C++ 3.0.1). - Fairly POSIX-compliant system header files. - The Rogue Wave Tools.h++ class library for C++ (version 7.0 or later, probably). - The TCL scripting language (we used version 7.3). - X11R6 or X11R6.1 client-side libraries (the server is not required). UltraSonix uses templates (paramaterized types) and exceptions in C++ fairly extensively. While it *might* be possible to excise all references to these features, it's probably not worth the effort. Unfortunately, C++ compilers differ widely in their support for templates. This is likely to be a problem area in the port. Most of the system calls we use should be portable across Unix variants. We tried to stick with ANSI and POSIX calls, but it's possible that a few calls slipped in that may not be found in some Unixes. One example is poll(), which is not found in Linux. (Isn't poll() POSIX?) The code uses a number of data structures internally to manage lists, hash dictionaries, and so forth. Right now, the code gets these from the Tools.h++ class library from Rogue Wave. This library is bundled with the Sun compilers, but costs a few hundred dollars on most platforms. The actual Rogue Wave classes are isolated from the rest of the source code by a set of wrapper classes. The wrappers all have names starting with a lowercase "m" (for Mercator): mList, mDictValPtr, and so forth. In theory, one should be able to rewrite these classes to implement lists, dictionaries, and so forth internally, rather than forwarding the calls to Rogue Wave classes. At one time, we used the LEDA freeware class library for these tasks. I'd advise against rewritting these classes from scratch, though, unless you have a serious C++ guru on hand to tackle the job. Writing *working* C++ container classes is not for the faint of heart. A much better approach (IMHO) is to break down and buy a copy of Tools.h++. I believe Rogue Wave does *not* require any license arrangement to distribute binaries containing this library. The core "engine" of UltraSonix is written in C++ and is responsible for handling interactions with RAP and the X server, and device I/O. TCL is used internally by the system to construct non-visual interfaces from the information collected by the C++ side of the world. A vanilla TCL distribution should work with the system with little or no porting. UltraSonix is an X11R6 (or 6.1) application. This means that it is linked against X11R6 client-side libraries. There is *no* requirement that it be run against an X11R6 server. Unlike client applications that must be linked against modified versions of the X libraries (see Section 5.0, Porting the Runtime System), UltraSonix should be linked against standard, vanilla, unmodified R6 libraries. 2.2 Other Issues Unfortunately, one piece of code that may be tricky to port is Resource.cc. This code handles the mappings from application-internal resource types to types that UltraSonix can understand. This code wasn't really "ready for prime time" in the sense that (1) RAP was still undergoing some changes about how to handle exchanging resource data between clients and screenreaders, and (2) the code is fairly rough, although it does work on SPARCs. The main issue for porting is byte-order and word-size dependencies. The code has support for translating, storing, and copying various primitive data types that are received from clients. The current implementation makes assumptions that the natural word size of the machine is 32-bits (like a SPARC, and like Intel), and that the machine uses big-endian byte order within a word (like a SPARC, but unlike Intel). This code will have to be ported; there should be some comments in the source file that can help. 3.0 PORTING RAP The Remote Access Protocol (RAP) is the means by which UltraSonix and client applications communicate with each other. Both the "agent" side and the "client" side of the RAP libraries must be ported. The "agent" library is that portion of RAP that resides inside UltraSonix (the agent, in RAP parlance). This code is straight C and should port easily to any big- endian byte order machine. There are a few places where a byte-swap needs to be done. If you'll be porting the RAP libraries, let me know and I'll figure out what needs to be done. See 6.0, below, for more issues about RAP. 4.0 PORTING DEVICE SUPPORT Device support in UltraSonix comes in two parts: loadable modules, and device servers. The philosophy of I/O we used is that the UltraSonix core should be written to be completely independent of the particular I/O devices that may or may not be installed on a given system. At runtime, and according to the user's configuration file, the system will load a number of shared object files that provide the device- specific support to, say, produce speech and braille output for particular devices. Most of the current I/O modules implement this support by talking to a network-aware server that is responsible for actually controlling the hardware. The idea here is that users may be able to share scarce resources, like Braille terminals, across workstations. These servers also allow hardware to be attached and used on machines physically separate from the user. The table below lists the loadable modules and the server processes that they talk to. I/O Module Type Server ===================================================== Dectalk Speech synthesizer dectalkd DectalkX Speech synthesizer dectalkd TruTalk Speech synthesizer dectalkd Genovations External keypad Alva Braille terminal alvad NetAudio Nonspeech audio netaudiod AudioFile Nonspeech audio audiofile The Genovations module actually opens a serial port and communicates directly with the keypad; all the other modules rely on a server process to actually control their devices. Porting the modules should be fairly trivial (with one exception, see below). Most of these simply open a socket connection to their servers. The servers may be *slightly* more difficult to port. dectalkd and alvad are very straight-forward and should port easily. audiofile is freeware from Digital Equipment Corp, and should run easily on any of its supported platforms. netaudiod is our "home-grown" audio server. It is fairly specific to the Sun audio device, and may take a bit of work to port. One module that may cause problems is the AudioFile module. Because of the particulars of the features provided by the Audiofile server, this module implements various audio filters internally. This work is done by a "work crew" of threads that are spawned when the module is loaded. Because this module is significantly more complex than the others, and because it relies on system-specific features like threads, it will be fairly difficult to port to new platforms. 5.0 PORTING THE RUNTIME SYSTEM Currently, applications require specially-modified versions of the X client-side if they are to be recognized by the screenreader. We have worked with the X Consortium to ensure that most of the changes needed by UltraSonix are present in the core distribution. This minimizes the required changes to port to a new platform. These changes, and their status, are listed below: Modification X Support UltraSonix Support ============================================================= Xt HooksObject Shipped X11R6 Supported XESetBeforeFlush Xlib Shipped X11R6 Supported hook ICE Rendezvous mechanism Shipped X11R6.1 Supported (see 6.0) Remote Access Protocol Not Shipped Supported RAP (see 6.0) Xlib GC cache hook Not Shipped Supported The first column lists the particular required modification. The second lists the status of the modifications with respect to the X Window System. Two of the required modifications shipped with X11R6; one shipped with X11R6.1; two--RAP and the GC cache hook--are not present in the X Consortium's release. The third column lists the status of UltraSonix' support of the particular modifications. Most are supported, although UltraSonix currently uses an older version of the ICE rendezvous mechanism and the RAP protocol (see 6.0, Future Work). Since not all of the required modifications are shipped in the standard X release from the Consortium, porters will have to create versions of the Xt and X11 libraries (*not* the server, just these two libraries) for their platform. Since a number of the required modifications are present in X11R6.1, it is much easier to start from these libraries than, say X11R5 (we have produced a set of R5 libraries that have all of the required modifications back-ported into them, although this code is a little rough). Making the modifications to the X11R6.1 libraries will mean that only applications linked against these libraries can work with UltraSonix. In general, to get the R6.1 libraries working with UltraSonix, you will have to do the following: - In libX11, add in a mechanism to Xlib to cache Graphics Context (GC) structures at the client-side. I can provide this code to any interested parties; it's quite short. - In libXt, add a single line of code to install the RAP protocol when the ICE Rendezvous mechanism is triggered. - Relink libXt so that the RAP library is bundled with it. The actual changes to the X library source code are very minimal. As mentioned above, you will have to port the RAP libraries themselves to your new platform. These libraries have only been tested on Suns, although they should port fairly easily to new platforms. There are a few places where byte-order dependencies exist; if you are beginning to port the RAP libraries, contact me and I can provide you guidelines for the (fairly simple) fixes. 6.0 FUTURE WORK The RAP libraries shipped with UltraSonix are essentially the sample RAP implementation done at Georgia Tech and provided to the X Consortium (see http://www.x.org/x-agent). Will Walker of Digital Equipment Corporation has rewritten the RAP libraries to be 64-bit clean and byte-order independent. Additionally, his code probably fixes a few bugs that are in the Georgia Tech implementation. Moving to this new RAP implementation (which can be found at the web page mentioned above) is, in my opinion, a fairly high-priority item. This new implementation is much cleaner and will ensure that UltraSonix will work on 64-bit architectures. Additionally, Will has provided the sample implementation of the ICE Rendezvous Mechanism that UltraSonix uses to contact applications. The version used in UltraSonix is *slightly* out-of-date. Moving to the new implementation would be a good idea. The code in Resource.cc and in RAP for dealing with the exchange of resource data "over the wire" is not great. This was an issue that was deferred in the discussions on RAP. Getting the protocol working well here is a serious design issue. If you've got everything else working, and have lots of free time you'd like to fill, drop me a note and we can discuss resource typing issues.