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

Why and How to build your own Chooser Extension

cheesestraws

Well-known member
There doesn't seem to be a good walkthrough or discursive tutorial on how to build Chooser Extensions out there, or indeed why one would want to. Furthermore, when I sat down and tried to learn it, it turns out that Inside Macintosh is wrong in some important ways and the other documentation I have is unhelpful, with one honourable exception.

So, over the next few days in this thread I'm hoping to write up a bit of a HOWTO, along with a fully working skeleton Chooser extension that I am going to upload to github.

But before we get to how, we should address the question of why you would want to, compared to, say, writing a control panel. Really, there are two reasons:

  1. First, a Chooser Extension is the idiomatic way to present a list of resources or devices connected to the Mac and getting the user to pick one under Systems 6 and 7. If the UI you want is a "network browsing" one, then a Chooser Extension is the right answer.
  2. It's actually rather easy. The Chooser manages the UI for you, and if you're interfacing with an AppleTalk service, then the Chooser will also manage the network code and service discovery for you. You get a lot of usability with very little code.

Of course, option 2 is an ambiguous virtue, because you lose a lot of control over the UI. Occasionally, one comes across software that is wedging far, far too much into a Chooser Extension, and because you don't have much in the way of UI control, one ends up with alarmingly complicated series of dialog boxes. Farallon Liaison comes to mind here, which is an AppleTalk router that is configured, for some bizarre reason, exclusively out of the Chooser. It's not ideal.

You can do a lot of the other things you can do with control panels with Chooser extensions too: perhaps most importantly, you can combine them with INIT resources in the same way, so you can have your chooser extension configuring your system extension. I'm not going to talk about that much here, because how to write INITs is a much better-documented thing.

So, to summarise: if you want a simple "network browser" or similar UI, a Chooser extension might be a good choice. If you want more, you might be better off looking at a control panel or, indeed, an application.
 

cheesestraws

Well-known member
Let's talk about the basic approach involved first.

A Chooser extension contains a single function that the Chooser calls whenever it needs you and a small pile of metadata. The function needs to answer questions from the Chooser like:
  • What items should appear in the list?
  • What item should be selected?
  • The user selected something or clicked a button, what happens now?
However, all the events are optional to handle, because a lot of the common useful stuff is controlled by the metadata. The first things the metadata does is control the user interface:
  • What is the list labelled?
  • Which buttons should appear?
  • What should they be labelled, and where should they be in the window?
But if what you're browsing is an AppleTalk service, you can also specify the NBP service to search for in the metadata, and the Chooser will just go off and do the service search and populate the list for you. So, if you're using AppleTalk, the only thing you need to do is handle button presses and, if you want to, what happens when the list selection changes.

If this sounds too good to be true, it is, in one important way. Your function needs to be basically stateless, because you don't get any global variables (or at least, I couldn't make them work). So doing anything complex with the selection the user has made needs to reside in more capable code, like a driver or an extension.
 

Crutch

Well-known member
Can you keep your global data around using a resource in memory? Put some stuff in a handle, then call AddResource. Then when you need it next time, access it with GetResource. Call RmveResource when you’re done. You don’t ever have to actually write anything to disk.
 

cheesestraws

Well-known member
Can you keep your global data around using a resource in memory

This is what IM grudgingly suggests, and what I'm ending up doing. But one gets the feeling that it's kind of outside the model that was intended.

Installation 3 is coming, may not be tonight though :).
 

LaPorta

Well-known member
Care to write network-based printer driver’s for modern printers? :p. Or, can anyone do the reverse, and write a modern driver for the AppleTalk ImageWriter?
 

Crutch

Well-known member
This is what IM grudgingly suggests

Ha - I saw that in IM Devices also which I assume you read. It is indeed “grudging” and I would not have had any idea what was intended if I wasn’t already familiar with concept from copious (very) old MacTutor articles.

It also grudgingly suggests stuffing stuff into the List Manager structure as I recall, which just feels weird. Using invisible List Manager cells as weird Excel-like grid variables? No thanks!
 

cheesestraws

Well-known member
It also grudgingly suggests stuffing stuff into the List Manager structure as I recall, which just feels weird. Using invisible List Manager cells as weird Excel-like grid variables? No thanks!

Yup! I'm not even going to give that one the time of day, because it's so plainly awful :-D. You can almost hear the authors adding "if you must" after each sentence. I'm going to use the resource trick in both the one I'm writing for the HOWTO and the one that I'm going to write after I've written the HOWTO (which is the reason I want to learn, but which I'm keeping a bit under wraps for the moment because I don't want to disappoint anyone if I can't get it working).

Care to write network-based printer driver’s for modern printers? :p

I'm afraid to say that printers and I Do Not Get On Well, and the less I can do to antagonise them the better...
 

cheesestraws

Well-known member
Scaffolding, Part 1: Everything not compulsory is still kind of handy

In this section, I'm going to walk through the minimum compulsory scaffolding for a Chooser extension. There isn't very much of it, and of course the resulting extension does nothing useful at all. The extension (and its paltry amount of source code, which for now you can ignore) is attached at the bottom of the post, and we're going to walk through it with ResEdit.

Let's start with the file details. You should pick a creator/signature for your extension (I've chosen 'DMRV', for "DeMo RdeV"), and give it the type 'RDEV' (other types are available, but you shouldn't use them).

Screen Shot 2021-11-30 at 17.20.41.png

All the stuff that matters is in the resource fork. It looks like this:

Screen Shot 2021-11-30 at 17.36.05.png

Nearly all of this is exactly the same as would be in an application, and it's all about icons. The only departure is the PACK resource, which contains the code itself. This PACK contains code that does precisely nothing (you can see this in the project attached below).

If we open the Chooser with this installed, we see this:

Screen Shot 2021-11-30 at 19.43.57.png

Because we haven't specified anything, the Chooser has decided that we want a list of LaserWriters. Nothing else will happen. The end. That was easy, wasn't it?

The project attached is a CodeWarrior Pro 4 project as is usual with me. Please feel free to bounce up and down on it.

In the next installment we'll get the Chooser looking for other network devices for us.
 

Attachments

  • mini-rdev.sit
    259.6 KB · Views: 2

cheesestraws

Well-known member
Part 2: Is There Anybody Out There? Getting the Chooser to query the network for you

The first job of a Chooser extension is to provide a list of things to choose from. Fortunately, if your service is an AppleTalk service (or something that passes for it), the Chooser will do most of the work for you. All you need to do is to tell it what to look for and how often to look for it. This is done by adding—you guessed it—some more resources.

Attached below is a second iteration of demo project. When you drop this into the Extensions folder (or System Folder under System 6), you'll (hopefully) notice that the list will now automatically be populated with a list of the AppleShare servers on your network (if you have any):

Screen Shot 2021-12-04 at 13.16.56.png

To achieve this result, two resources were added, one saying what to look for, one saying how often to look for it.

The first is 'STR ' -4096. It must have this ID. This resource should contain the service name (in NBP) for the service you're looking up. For example, AppleShare servers advertise themselves as 'AFPServer'; LaserWriters are (all together now) 'LaserWriter'; Timelord time sync servers are 'TimeLord'. In our case, I've set it to 'AFPServer'; if you have other services running on your network, you can change this and see what happens.

Screen Shot 2021-12-04 at 13.37.27.png
The second is 'GNRL' -4096, which must also have this ID. Note that if you're using ResEdit here, you have to manually invoke the Hex Editor from the Resource menu, because this type of resource appears to be used for multiple different things, and ResEdit has an editor for a different use of this resource.

Screen Shot 2021-12-04 at 13.39.50.png

This resource contains just two bytes, and by carefully using different terminology from the rest of Inside Macintosh, IM: Devices manages to make them confusing. They aren't. If you just want to get this done, use 0x07 0x05. Otherwise, read on:

When a client looks for a network service, it broadcasts several lookup requests, inviting services to tell it where they are. It doesn't just broadcast one, because either one could get lost on the way, or a device could be too busy to process it* (for example, on early LaserWriters, I believe the lookup process is one of the least prioritised tasks on the CPU, so if it's doing something else, it'll just ignore one). To make this work, you need to tell the lookup process the interval between lookup requests, and the count of lookup requests to send. The Chooser runs this lookup for you every so often: you do not have control of how often it does the lookup.
  • The first byte is the interval between lookups. This is measured in units of 8 ticks (about 1/60 of a second).​
  • The second byte is the number of lookups.​
There's obviously no single right answer for these parameters, which is why they're parameters in the first place. However, IM: Networking, which is more useful than Devices here, suggests starting with values of 7 for the interval and 5 for the retries. This makes the whole process take about a second, and still provides enough retries to catch recalcitrant services. And if you look inside the AppleShare extension, this is precisely what AppleShare uses, so I suggest you start with these two values in your GNRL resource and tweak if necessary. Here's the GNRL resource from the mini-rdev:

Screen Shot 2021-12-04 at 14.00.13.png

These two resources, 'STR ' -4096 and 'GNRL' -4096 are all you need. Your Chooser extension can now look up AppleTalk services!

--

* Observant readers will note that this is a big design difference between NBP and DNS; DNS, a more centralised service designed by optimists, generally assumes that nameservers will work; Apple, more pessimistically, assumes failure to start with and tries to work around it, which results in more reliable discovery at the cost of a chattier protocol. Swings, roundabouts, you know how it is.
 

Attachments

  • mini-rdev-2.sit
    259.3 KB · Views: 2
Last edited:

cheesestraws

Well-known member
Those of you who are quicker on the uptake will me will have noticed that string literals also seem to be register-relative positioned. So you can't use those, either. I might have been beating my head against this all afternoon.

New installation to follow...
 

ymk

Well-known member
Thanks for putting this together. I anticipate I'll be attempting this at some point.
 

Crutch

Well-known member
Re string literals - I don’t know if CodeWarrior supports this natively, but you can recover a pointer to your code resource when it starts running and use it get your string literals if stored at a known offset. In THINK C you do this with the stuff in SetUpA4.h, which grabs A0 on entry and stuffs it into a DS.L instruction inside your code block. (This could break in a multi segment code resource if your segments aren’t locked.)

Or of course you can use resources, but you already thought of that :)
 

cheesestraws

Well-known member
In THINK C you do this with the stuff in SetUpA4.h

I did try using the A4 stuff in the same way as I would for a more "normal" code resource, and the Chooser seemed to get extremely confused. I may have been doing it wrong somehow, though—for the project I actually want this for, I'm counting this as "a win for internationalisation", though, and probably not worth chasing too much.

That's not to say it couldn't work—I'm sure it could—I'm just saying I couldn't get it to work. Which is a much less surprising statement :)
 

ymk

Well-known member
It looks like you're using a bit of assembly to place the code in a specific resource type.

I'm having a tough time doing the same with Symantec C 8.

Considering this is essential for an extension, driver, control panel, etc, I can't believe there's not a more straightforward way to do this.

GNU ld is a breeze in comparison.
 

cheesestraws

Well-known member
It looks like you're using a bit of assembly to place the code in a specific resource type.

The assembler code is the header for the code resource, not to define what kind of resource the code goes into. The standard Apple format for code resource headers repeats the type of code resource it is—I'm sure there's a reason for this, presumably a sanity check, but I don't actually know what the reason is. The kind of resource it's going into, in CodeWarrior, is defined in the target settings: I tell it it's a code resource project, the kind of code resource it is, and that I want a custom header. I'm not sure where all those settings are in Symantec C; I've only used much older iterations anyway, but it was in those I'm pretty sure...
 

cheesestraws

Well-known member
Symantec C 8.

I don't have Symantec C 8, but I have THINK C 5.0, which I believe it is, at length, derived from. In THINK, it's under Project->Project Type—set it to code resource and enter the type there. If you want a custom header, there's a custom header check box in there then you insert your header as an assembler block at the top of main.

Again, in THINK C 5.0 there's a "Sample CDEV" under THINK C:THINK Demos:sample cdev — probably moved in newer versions but hopefully that gives you some clues to start from.
 
Top