Mac OS • 57:57
Focusing on user interaction for interactive applications, this session covers implementation details of both the HID Manager and Carbon Events for Mac OS X. Key issues such as getting mouse deltas and finding devices are covered thoroughly.
Speaker: Geoff Stahl
Unlisted on Apple Developer site
Transcript
This transcript was generated using Whisper, it has known transcription errors. We are working on an improved version.
This is session 135, Input for Games. And I'm Geoff Stahl, Games and Graphics DTS Engineer. The good news is this year, as we're talking about HID Manager, we're talking about real input for X, we're talking about devices that you can really get into your applications, use them, and make some great games and any other application that you may be interested in that uses input.
We're going to go through a number of things today. What I'm going to basically start talking about is the HID Manager and how it attaches to the system, where it sits, and then how to really use the HID Manager. I'm going to show a lot of examples. We're going to walk through some of the I/O registry and look at how the devices are actually laid out so you get an understanding of how the devices actually interface to the system.
First thing we're going to talk about is system services for input. We're going to talk a little bit about Carbon and about what's there and why you may also want to use Carbon and why you need to use Carbon right now for a few devices. Then we're going to talk about the HID Manager and the meat of the discussion will be about the HID Manager, will be about how the HID Manager works, and will be about how to use it and how you can use it in your application.
Lastly, we'll use the HID Manager for getting user input. First thing we'll do is use device discovery. How do you go through the system, use the services there to actually discover what devices are on the system. We've done a lot of the work for you and we have some sample code that will be out either later today or this weekend that you can peruse over that will be the basis for the code that's in this discussion and you can use that directly in your applications for device discovery and for getting input. Then we're going to be talking about retrieving input. As far as retrieving input goes, you're going to have to decide exactly how you want your input, where it's coming from, whether it's an axis, whether it's a button, and know how to get that into your application.
Lastly, we'll talk about input configuration. One of the big things for people playing games or any other use of the input devices, if you have your jog shuttle you may be using with your image editing application, your photo editing or your Final Cut Pro or Premiere film editing application, one thing you may want to do is the way you design it, and the way the user wants to use it may not be exactly the same.
So you want to have a way to configure that. You want to have a way for the user to use any buttons for anything they want. Whether they're navigating the web or whether they're editing the next great movie, you want them to be able to configure it however they want.
So let's talk about what system services exist on Mac OS X for input. HId Manager is built on top of I/O Kit. It is a fairly thin layer on top of I/O Kit that actually handles device communication and allows you to get input from devices. It's fairly robust in the fact that it's not an API that limits you and how you want to use it.
You can use it at a high level with an Aqua interface or you can use it in an immersive game with your own interface. It doesn't limit you as we've seen in the past that you may be limited to only one way of configuration or only one way of dealing with devices. This allows you to deal with them however you would like.
You can use joysticks, jog shuttles, universal power supplies, game pads, anything that is a USB device or a HID compliant device, actually that is a HID compliant device, will work with HID Manager. Some USB devices, if you look in the I/O registry, you'll see they don't have a HID, they're not supported through HID. Those devices would have to have a special driver to work through the HID Manager. But most devices that you'll normally see in consumers, your game pads, your joysticks, your jog shuttles, will automatically support plug and play HID recognition.
Carbon handles mouse and keyboard for right now. Mac OS X currently, for Carbon, to get mouse and keyboard that includes mouse deltas, multiple mouse buttons, mouse wheels, all that can work through Carbon Events and can be integrated with the HID Manager very simply. We're looking to the future to bring that all through into HID Manager and so you can get all inputs through the HID Manager at one stop shopping.
Let's talk a little bit about Carbon Events. Carbon Events allows access to multiple buttons, up to 32 buttons, and mouse deltas, which is key if you're doing a game or you want something other than tracking the mouse on the screen. Git keys and Git mouse also work currently. So if you have an application that you just want to quickly code up or you want to do some basic input, you can still get the mouse position with Git mouse.
Realize that it's pinned to the screen coordinates or to the full world coordinates, whatever you're supported by your screens, and thus you may not be usable without some hackery to keep the mouse in the center of the screen or on the screen. There's a better way to do it, and I'll show you how to do that today.
[Transcript missing]
Discussions already this week about Carbon Events. I'm not going to go into exactly how to use Carbon Events. There's a great book on Carbon and how to use Carbon Events. There's some great sample code in the Carbon SDK. Please look there. Look through it. It'll bring you up to speed on the basic use of Carbon Events.
But what events are you very interested in to get for getting the mouse? We're talking about kEventMouseDown, kEventMouseUp to determine whether your mouse is down or mouse up. You can track those and see whether the user has a mouse down or mouse up. When you do that, one thing you want to look at is you want to look at the actual what button there is.
There's a button param I'll go into a little later that tells you what button is going down for the mouse down or mouse up. So just the mouse down or mouse up event says some button went down. If the user has a multi-button mice, it could be two different mouse buttons that go down or up in different events there.
Mouse wheel moved, mouse dragged, mouse moved. The last two are the key things to determine for the mouse moved events. You're not going to find a mouse delta event. What you see is you get a mouse moved event and then you look at the mouse delta in there.
So you're actually when every time the mouse moves, you're going to get a mouse moved event and then you look at the mouse delta parameter to find the delta from the last time you got the mouse moved. Realize that this updates on a per event basis. So you don't want to accumulate, you don't want to skip events, you want to accumulate that delta every single time you get a Carbon event. So that's kind of key. One thing to note is if you're in Cocoa, you can use the core graphics to get the mouse deltas.
Again, those need to be synchronized and the fact that you can't wait half an hour to get a mouse delta and expect it to be an accumulation of all the last mouse deltas. Or if you pull very very quickly and get the mouse delta, you're not going to blank that last mouse delta. It's going to be the same one over and over again. So realize there is a synchronization issue here when you're doing this.
Disassociating the pointer. See if you disassociate mouse and mouse cursor position allows you to dissociate the pointer from the actual cursor position, so it will allow you through core graphics to not have the pointer move. You can hide the pointer, call that routine, and the pointer is not going to be moving underneath your application.
Handle Raw, Key Down, Handle Raw, Key Repeat, or Key, K, Event, Raw, Repeat. Very simple. If you want to just get keys, like a user would be doing typing, you'll handle those two events. Key Down, you get a key, Key Repeat, you get the same key, and you continue to handle those.
Or you may want to do a key down, key up events. You want to, when the wall, the key is down, the user holds the key down, you have thrust going to your rocket ship, or you have your laser beam coming out, or whatever your game has in it.
You can, when the key is down, you can activate, when the key is back up, you release instead of doing a polling on the key downs and key repeats. And one other thing to look at is key modifiers change. That will allow you to get the modifiers, figure out what modifiers are down on the keyboard.
So let's move into the HID Manager. The HID Manager basically handles input output from devices. The device discovery is actually handled through the I/O Kit services. It allows access to device elements. The key here is that you get a device, you get a joystick, and that joystick is built out of elements.
It's built out of an X and a Y axis. It may be built out of a hat switch. It may be built out of a slider and some buttons. All those put together describe your joystick. So what you're going to see when you look at it, you're actually going to see something that tells you it's a joystick.
Below that there's going to be a list or a collection of all the input pieces of that joystick. And that's how you need to think about it when you're thinking about your game and mapping this to actions. So when you have this, what you'll do is you'll say, "I want this device, my joystick.
I want to get the X axis, and that'll be the X axis for my game. And I'll assign that to some action in the game. And that action in my game will be turn left, turn right." If someone then comes in with a gamepad, the gamepad will also have a collection of elements. They may also have an X and Y axis. And you then can assign that, the X axis in the gamepad, also to turn left, turn right.
HiD Manager will provide polling and queuing services. You can either do a kind of a poll. You can get current values of any device and any element out there on the system. Or you can queue events on a per element basis, which may differ from some other APIs out there as far as how you actually poll and queue events. And on that note, I think, why don't we take a little sidebar here.
and talk about HID Manager as compared to direct input as compared to input sprocket. Just give a little, to have a cage everyone's gyros as far as exactly how they compare, compare and contrast those two. So if you're familiar with one of those other APIs, this will kind of bring up the speed and where HID Manager stands in relation to that. Then we'll jump into actually working with HID Manager.
So first, device discovery. The HID Manager uses I/O Kit, as we talked about, for device discovery. Direct input, kind of similar, it has its own is a developer at IOCit, its own discovery services, but very similar as far as you create a direct input object and you're going to enumerate devices.
Similar thing to what you're going to do in IOCit as far as you create an object and you'll enumerate on the devices. So very similar methodologies there. Input sprocket, you can use ISP devices extracting, you'll list through the devices there, which is the same methodology. Or the high level input sprocket you can just use on ISP init, which will actually go out and grab the devices for you.
Element handling. We build a HID device interface and then we look for a cookie. So we basically have an interface and a cookie and we go through I/O Kit to actually find the device and then get the element off the device once we have an interface to the device.
So what you're going to look at is you'll have a device interface and then you just give it a cookie and that will allow, that's exactly what you need to get information about that element. So there's the two pieces of information you need. Again, you need the device, you need that cookie that points to that input on that device.
Direct input, you're going to enumerate objects and you're going to use Git capabilities to identify the capabilities of that device. Direct input is very device centric. When you get things out of direct input, you'll get a device at a time. You don't get an element at a time. HID Manager on the other hand is element centric. You can say I want to queue this one element. So I'm only interested in one element.
I'm not interested in the entire device. I'm interested in one piece of that device. Input Sprocket. You can use Input Sprocket Get Element List to retrieve input of elements. And you can get the references elements through that list and then use that to get the information about that element.
User configuration. User configuration for I/O Kit, I'm sorry, for HID Manager is handled through the application however you want to do it. There's no built in services currently for high level configuration dialogues, but as you'll see in some of the examples, I've given you some tools that will be real easy for you to build your configuration dialogues and/or if you want to do a fully immersive API, build them into a fully immersive API.
Direct input, again, handled by application. The application needs to handle the configuration in some way. In some cases there is utilities provided by the device vendors that handle some mapping of those devices, but again, that's an application or device specific thing. There's no system services specifically for input configuration.
Input sprocket. We have an ISP configure that we could use on 9. One of the big things we've had feedback from the developers was that works great for a limited subset of cases. If you have a fully immersive app, all we give you is a dialogue box. If you're full screen, we give you a dialogue box.
There's a lot of problems with some of these things, so it really didn't work completely well for all developers. What happens is that when you want to stay immersive, you don't want to bring up the Mac dialogue and there was no real way of getting information out. Well, in HIID Manager, we have full services to get that information out so you can build whatever kind of dialogue or immersive UI that you'd like to.
So for input retrieval, very simply, we're going to use a good element value for polling, and we're going to use a queue for querying, and we're going to get the next event. Pretty simple. We give it the device, give it the cookie for the element, and we get the element value, or we put that same information into the queue to queue it, and then we get the next event.
Direct input, we're going to get a device state. So basically direct input again is very device centric and you're going to get the entire state of the entire device and you can sort through it and figure out exactly what you want. Input sprocket, you can use ISP, get simple state or get next event, very similar in this case to the HID Manager when you're working at the event level.
Okay, let's talk about how to do things with the HID Manager. Device discovery. First we're going to set up an I/O matching dictionary. Then we're going to get the device iterator. Then we're going to iterate for devices. Then here's a key, we have to recursively iterate underneath that through CFDictionary's and CFArrays for the elements. We'll capture the cookies for the element identification. That seems pretty complicated.
We're going to break this down. I'm going to talk about how to do this. The first thing I want to do though is I want to go through the I/O registry and show you exactly what the devices look like. A real good thing when you're working with your applications is you can get right in there and see everything that's on a device.
You can see if your application is correctly seeing the device, see what actually the device is reporting back to the system. You can really easily make sure that you have the correct information on the device and you understand what kind of information you're working with. Let's bring up demo machine number two.
And this is where you start with the I/O Registry. I/O Registry Explorer from Developer Tools Applications. Start at the root. And this is my, you know, I'm not an I/O Registry guy, so as far as do you need to be an expert in this? No, you don't. Not at all.
I'm not an expert by any means on exactly how the I/O Registry works, but I was able to very easily put together the HID samples and I think you'll be able to easily use this. So we're going to walk through the I/O Registry, my secret path here, which is Power Mac 3.4, Mac RISC, and then the PCI.
Here, and if you notice, you have USB 18 and 19. You look on here and There you go. If you notice down here, you have a Mac Oli USB joystick, you have a GamePad Pro, you have a Mac Oli iShock, and you have a jogging shuttle. Those are your HID devices.
And when you look at the I/O USB interface, you'll notice it has a HID driver. You'll notice this one has a HID driver up in this area. You'll notice this one has a HID driver. So they're all HID devices. So let's look at, just pick the Mac Oli USB joystick, look at the HID driver. So I'm looking at the elements.
What you notice is, I talked about the device being the package of elements. You notice that elements is an array, and of that there's five dictionaries. And then if you keep looking at elements, you can go down and look at the types of elements there are. And what you'll notice is, let's drill down to one of these.
Here's an element, for example, has an isRelative value, has a null state type, preferred state, nonlinear, wrapping size element cookie. Right there is exactly the piece you need to save. That element cookie per device is specific, so this element will only be referred to by that cookie. No other element will have that cookie. Has a min and max. In this case, based on the min and max, this is a button. The other thing we look at is the usage and usage page.
While this may seem low level, it's real simple and this is what USB spec and HID spec is built on, the usage and usage page. It's really important to understand that that defines what you're looking at. Anything from a joystick to a button to a dial to a hat switch is defined by usage and usage page. And we'll look at that in a second to show exactly how that looks. But what you'll notice about this structure is it is an array of CFDictionary's. Has a min and max.
Has a null state type, preferred state type, preferred state type, and has a min and max. and you work down the dictionaries. Here's another example of something. In this case, this is a slider or an axis. You see the min value is 0, max value is 55, usage is 54. So how do we figure out what the heck this thing is? You bring up demo machine number 3.
What you'll see is, I'm trying to left-handed mouse, so we'll see how this works. What you'll see is, this is the HID USB parser dot H in the I/O Kit framework. This is a very long list of everything you ever could want to know about what's on a device.
The main pages is what you have right here. Usage pages is an undefined generic desktop simulation, VR, etc., etc. You notice some things that are interesting. There's a button page and there's also a consumer page. Almost everything you want to work on is in the generic desktop page. We move down to the generic desktop page.
Again, as I said, As I said with the generic desktop page, everything from the actual description of the device down to the elements of the device is on this page. So, a joystick would be a generic desktop type 4, which is a joystick, while a gamepad would be type 5.
On that gamepad, you might have GDX, generic desktop X-axis or Y-axis on that same device. So there's not like this is a list of devices and this is a list of stuff on the devices. Everything's in the same kind of list. And this is your list, this is your dictionary, so to speak, how to convert from the numerics of it to actually what the things are.
So you'll see there's a dial, wheel, slider, hat switch. You also have things like velocity X, velocity Y, some strange things, system control, system sleep. So if you have more advanced devices, you may have that kind of stuff. And what's interesting if you go down this, it's kind of fun to look at.
This is set up for doing things like the Disney rides. And they'll use the HIID spec. You know, this tank simulation device or magic carpet simulation device. I don't think I've seen that in the store recently. And you go down, flare release, flight communications, trigger, weapons arm, wing flaps, rear brake, front brake, handlebars. Oh, here's a rowing simulation. You have oar, you have a slope, a rowing simulation. You have a rate of stick speed. Oh, and then you have for golf, if you need your five iron or your loft wedge.
And oh, they're playing pinball here, bump, secondary flipper, flipper. So this list is very generic for everything that uses HIID type of devices. Some things we want to look at is keyboard. First, for keyboard support, this keyboard page will be the mapping. And this section you're seeing right here does map to the ASCII values.
So the standard numbers will map to ASCII values when you get those back out as far as what elements of the keyboard are pressed. And then you can have additional things like your F keys, your page up and down, and so on. page down, your left arrow, right arrow. Moving out of that, you have some LEDs, and there's some great LEDs in here also. You have a night mode paper jam, so just in case your game wants to show that you have a paper jam.
Buttons, once you get to the button page, buttons are very simply enumerated down the page. So button page number 12 is button 12. You can see they just list a few of them, because it's pretty obvious. And there's also telephone is listed on here. And the consumer page is interesting also, because, for example, I have this jogging shuttle, which lists itself as a consumer control device. So it just lists the consumer page and it says I'm device number one there.
So that's something else. And you notice consumer devices a lot of times are VCR kind of things or media control kind of things, play, record, fast forward. But you can see that someone could use a jog shuttle and put the consumer device page in, put play, fast forward, rewind, all those kind of things. And you could definitely, applications should take advantage of it.
For example, it would be great to have your little jog shuttle with iTunes. Let me go to the next track. Let me back up a little bit. Let me skip around. Those kind of things. Or if you're doing something like Final Cut Pro X, you could control your movies directly. And you could take one and plug it in. You could take this jog shuttle and plug it right in. Or you could take some other jog shuttle and plug it right in. And it would all work the same. That's the idea behind HID.
So we've seen where the I/O registry is. And I think everyone should at least look at the I/O registry and see the HID devices and see how they're listed and see the information that's on there. Because the information that we're talking about here, and I'll show you, is basically the information out of the I/O registry. And it's really pretty simple to do. It's really simple to use into your application. So let's bring it back to the slides. And we'll talk about how to get information out of these things.
First, the caveat to all this. I have sample code that will be going up either today or this weekend that will do all of this for you. So, you don't have to, well I'm going to tell you all about it and we're going to talk about it. This will go through and build a list of devices. This will discover all the devices for you.
This will get all this information out of there. You can look at how it's done. If you don't like it, if you think it's not done properly, you can change it. I would love some feedback and we'll keep this sample code up to date. I'm working very hard with engineering to make sure that this is done the correct way.
And we'll reflect any changes that we make in APIs or in I/O Kit or something changes in the OS that I may have found that are mistakes I may make. So, let's talk about the wonderful setting up the I/O matching dictionary because everyone should know how to set up an I/O matching dictionary. The I/O matching dictionary, what it's going to do is basically going to say I'm going to go into the I/O registry and I'm going to find everything of the type you want. And that type is going to be joysticks, game pads, jog shuttles, whatever.
So, in this case, first we're going to say we want to match HID devices. So, our key is we want to say HID devices only. I don't want to find the USB hard drive. I just want HID devices. Then we're going to say we're going to do the CFNumber create which is some standard CF for creating numbers. And we're going to set values.
We see a ref usage page and ref usage. That's exactly what we just talked about. What you do for the page would be generic desktop. And for the usage might be, would be in this case, would be game pad. And it would find, you're looking for the game pads in this case.
So now we want to get, once we have a match, we want to get an iterator to iterate over all the listed devices. This is pretty simple. All it is, is a list of devices you get and you want to iterate from first one to last one. You need the I/O master port, get the access to the I/O registry. You're going to use setup matching dictionary and that was what we just saw a minute ago to get the matching dictionary.
Once you get the matching dictionary, you're going to get I/O service get matching services. And what that's going to do is take some matching dictionary, takes the master port, puts them together, and gives you an iterator for your objects. So that gives you something you can iterate across all the device list and pull your devices out.
So this is going to show you how to use that iterator. First thing we do is we're going to get an I/O device by continuously looping through the next iterator. Then what we need to do is get the dictionary for the HIID properties. As you saw, all the properties for that device were stored in a dictionary. So we want to get that dictionary right there. We get the mutable dictionary ref.
I/O Registry creates CF properties and we use that with the I/O device we found. I have my variable called HID_PROPERTIES that we're going to fill with the property list. And then what we're going to do is we're going to go across and the top level will be the info about the device. So the top level of that dictionary will show you about the device.
What kind of device it is, what the vendor is, who the product is, the serial number, what else will show you. Location ID, which is very important. Location ID is a long, this kind of a mangled long that has an encoded where in the whole tree of USB LAN that thing plugs in.
So if I have two game pads and you guys are playing with one game pad and you guys are playing with the other, I don't want in the middle of the game to have that swap and not be able to figure out which game pad is which. So the location ID makes sure that you can identify a device, specifically location, if they're exactly the same devices plugged into your system.
So that's in the device info. And then below that we have a collection of the element information. And in that collection of element information is going to be all the information of all the elements and we're going to step through that. And once we're done with it, we're going to release the properties.
I'm going to try and go through this and explain exactly kind of what this does, but if you want more detail, I say you look at the code. The code is fairly well commented, and you can step through it and see how it works. The key here is that you have to recursively step through kind of a dictionary array pairs. So every time you get an array, you're going to step through and you should get a dictionary below the array, and in the dictionary you should have another array.
So you come back and do the same thing again. So this is what this starts out doing, and we have kind of one level that handles the arrays and one level that handles the dictionaries. You pass a dictionary into this level, and this is going to say, it's going to get the value of the properties and get the type ID.
If it's an array type, which is what we're expecting, we're going to say, you have a dictionary at the top level of the device for the elements, and you may have an array of 50 elements below that. So you're going to get the array, and then you're going to look at CFApply or ArrayApply function for the entire range of that device. And get my array elements, which is the next function I'll show you. And you're going to swap that all back and forth.
So once you get the array, what you'll then do is call the array applier for each value in the array. And in this case, what you'll notice that if it's a collection type in this array, we'll go back to the dictionary and look for more arrays. So for example, a device may be organized as a joystick. You may have X and Y axis grouped together.
You may have some controls on the top of the joystick grouped. You may have controls on the base of the joystick grouped together. So it may not be a flat file. So there is some intrinsic there as far as how the device is actually built, is built into this information. So in that case, you have a device.
You may have an array of two elements below that. One element will be the stick. One element will actually be the base. Inside the array of the stick, you may have five elements inside of that, an X and Y axis, which is grouped together, which is a group. And then you may have buttons that are outside of that. So this makes it a little more complicated as far as how you extract things from devices. There's not a specific order to expect things or it's not flat.
This code is what does that. And what it does, the key here is where the switch. If it finds a miscellaneous, a button or an axis type, it says, hey, I'm interested in that. That's an element that I'm interested that's going to give me input. So I'm going to go get info about that element.
If it's not, again, if it's the collection type, it says that's another array. That's another, that's the stick. That's the base. That's the X and Y axis grouped together. I'm going to go back and get the dictionary for that one, or with the dictionary for that one, and get another array and press down into the hierarchy.
So get my element info is pretty simple. This is just a little piece of it. Basically what you want to do is see if dictionary get value with the I/O HID element cookie key. And where do you find the I/O HID element cookie key? You find that in the I/O HID lib.h, and it has a list of all the keys that are in the I/O registry for all the HID devices. Everything you want to know about a HID device is listed in there.
You pass the key in, it spits out a value to you. It might be a string, it might be a number. There's a couple booleans in there also. And that's real simple. And then you go get value for that, and that's how I got the cookie for all the elements of every device. And you just loop through the entire device for doing this, and you pull out all the information there.
The other stuff we talked about in the I/O registry is what makes up a device. So type, an element, that was what we talked about before, whether it be a button, an axis, a miscellaneous type. This is a usage page, which may be generic desktop probably. This is a usage, which would be button. Axis slider, X rotation Y, what are the, what do you see? Rotation Z. You may have something just generically called a slider.
[Transcript missing]
And the last thing, the name of the element. Which, for element-wise, I have not seen one name filled out. So I've never seen a device report itself, "Hey, this is button six," or "This is the hat switch," or "This is the trigger." I've always seen button one. So that's something that you may have synthesized yourself, or my code synthesizes for you based on the usage and usage page.
So let's talk about device discovery and see what we've already done for you. Demo 2 machine, we'll bring that back up and we don't need to look at the registry again. And this is a piece of sample code that will be ready and it is done, so it will be either up today or this weekend complete and it has the complete code to do a device discovery, get a list of devices and tell you every piece of information there is about the device that we can find. Geoff Stahl In this case I have a key span USB hub here and I have four devices plugged in. First my Mac Alley USB joystick, Gamepad Pro USB, so the standard Gamepad Pro.
The MacAli iShock, right there. And lastly, what's called the Jog and Shuttle, which is this jog shuttle device. The no additional code was done for this part. All those names came directly from the device. So I just looked up the device and said, "What is the name of the device?" What the code that I have, a sample code does, it goes and makes a list of every device and every element on the device, pulls all the information out so you have a flat structure to access it. So you can just go device, pointer to device name, and that's the string that represents the name of that device.
If you notice, this is a joystick. It has two axes, four buttons, one hat, one slider. So the hat's up here, sliders on the base, axes, and four buttons. Vendor ID and Product ID. Why do you care? Vendor ID and Product ID are very important. That vendor ID combined with that product ID identifies that specific device. The next version of that device will have a different product ID. Another vendor's joystick will have a different vendor ID, even if the product ID is matched. So those two devices identify the Mac Alley USB joystick specifically.
So if I find one of these on anyone's system, it's going to be this device. So if I have a configuration that someone has put together for this device, and I find one, it'll work on that device. Guaranteed it's the same kind of device. If you combine that with this big long location ID down here, what that gives you is that this device, plugged into that specific port on the hub, plugged into the specific port on the machine. Which is actually a lot of work. Which is actually on the keyboard, which is then plugged into the machine. The reason you have that is when you run the game or application again on that one system, you can say, that's the exact same joystick I had before.
Someone didn't plug an additional one in, it's not two joysticks plugged in, it is the same joystick probably sitting in the same place on the person's desk that you had before, so it's really easy to do configuration. One thing you'll notice here is that serial number is blank. I haven't seen a serial number on any devices, but if device vendors start using serial numbers, a serial number would be a way to identify this particular joystick. joystick.
What else do we see there? Product, obviously Mac Alley USB joystick, manufacturers Mac Alley. Usage is a joystick. It's a generic, the 01 we saw was a generic desktop page and 4 is a joystick so it says this device is a joystick. Here's a list of all the stuff it has. Let's look at the x-axis because that might be most interesting.
It's a miscellaneous input type. It's an x-axis. Again, all we did was look up in the generic desktop page, usage 30 was an x-axis. It's cookie is 7. If you notice, if we go to any other... Any of these other inputs, the cookie is not 7, so you can identify specifically that x-axis with that cookie.
So anytime you're referring to that cookie, that's the exact same element. Range it says is 0 to 127, no scaled range, size is 8 bits, so it's returning a single byte, and you can see that it has a preferred state and none of these are checked. No vendor specific information, it doesn't have a units or a name.
So if you look at the bottom of that, the raw value I'm getting out, you can see the raw value that you're seeing is about 121 to 1, so it's not bad. That gives me a lot of value. My centering is not quite right, and I can obviously adjust the centering through the joystick, which if you notice is not that accurate or that linear.
Once you start looking at the devices, you realize how not wonderful they are. One thing that I did was calibrated, and we'll go to the Y axis to do this easier. I should go to the slider because it's easier doing the slider. Slider hasn't moved yet, so I have no calibrated value.
My calibration is pretty simple. All it does is look at the max and min range that it's gotten and calibrates that to what it's supposed to be. So in this case, if I move it just a little bit, it tries to map that little bit of movement to the entire max range.
The reason it does that is it says if your slider, in this case, goes to 7 and goes up to 126, huh, yes, there you go, 255, 126, that's the sliders all the way thrown into one extreme. If you had this device and you were expecting 255, you're only going to get 126. But the calibrated value, which is in the sample code, maps that 0255, so you can get what you expect out of it.
And then the bottom is just a scaled value that says, hey, if you want to scale your y-axis 0255, it's going to scale your y-axis 0255. Again, y is an example too. It goes to 12, and that's full throw. And on the other side, it goes to 125 instead of 127. So you can see that these devices don't always go to the extremes, and you can expect that. Let's look at some other devices here. The GamePad Pro.
We can figure out what button that is. That's that button right there. We also have, look at the X axis there listed, and you can see the X axis, 255, 0, 127 for the middle in this case. So different ideas of how they want to set up axes. They're not always set up the same. In this case, the scaled value, again, scales everything to the same values. That's probably the only thing exciting there.
What do we have on the iShock? The iShock has three axes, twenty buttons, and one slider. If you notice, the iShock is set up with what looks like an axis here and looks like an axis here, an x and y axis. This is x, vertical is z, vertical is y, and vertical is z.
[Transcript missing]
A Z rotation, and that should be side to side is your Z rotation. And if you notice also, that's the full throw again, 119.
The slider is the vertical, which goes from 118 to 13. So again, be careful with that when you're working with the devices that you're not expecting full range or you don't have to have full range or you're mapping at least a full range. So that's something there. The last thing we'll look at in this case is a jogging shuttle.
Interesting thing about the Jogging Shuttle, it's a consumer device, has 13 buttons, one dial, and one wheel. If we look at the wheel, which is interesting, it's a, um, no, actually we want to look at the dial. You notice the dial is a wrapping element. The dial wraps around. So it's kind of a neat little device there. So you can see how the indication from the element tells you exactly how it's going to behave. So we will quit out of this and go back to the slides.
Okay, luckily the code here is definitely not the density of the code in the first section. So we're going to talk about setting up the interface, polling event queues and Carbon events. Well, maybe I lied.
[Transcript missing]
I have K device size queue. But if that's 10, you've got to be retrieving data every tenth of a second to even keep up with the input from an axis.
So realize that you may want to pull axes, queue buttons. Or if you do queue axes, you want to retrieve data with a fairly large queue at a fairly good rate. Also, the queue is actually locked down in kernel memory. So it's held in RAM. So large queues, if you create this obnoxiously large queue, is a new memory that you're committing to holding down in RAM that will not be released until you release the queue.
You add element to the queue, and then you start the queue when you need to start the queue. You also can flush queues when you need to to flush the events out of there. So get next event, real simple, you have a queue, you get the event out of it, it gives you a time in the event, it's pretty easy, it gives you the device in the cookie, so it's real easy to find out exactly what caused the event.
Let's talk about Carbon Events a little bit and then we'll go back to some examples on how this all works. In Carbon Events, what we want to talk about is mouse drag, mouse move. I talked about those earlier. What you want to do is get event parameter, the mouse location, and it gives you a mouse point. Real simple.
So this is how you would get the mouse point of the actual mouse moved event or mouse dragged event. If you want the delta, kEventParam, mouse delta. This is not in the very top level events in the CarbonEvents.h file. It's down in the bottom, but it just works just the same. If you ask for that parameter, you'll get the mouse delta for any mouse moved event.
Mouse down event. Things you can get out of it. You can get the mouse location, so you can get where they press the mouse down, so if you're tracking controls or doing that kind of thing. You also can get the modifier keys. So if you want to see what modifier keys were held down, command click, shift click, those kind of things, you can do that. And lastly, you can get the mouse button. So you can get more than one button. They're enumerated in order. So if you want to get primary, secondary, tertiary buttons, you can do that. So support multi-button mice. Very easy to do with Carbon Events.
Lastly, Carbon Events for keyboard data. Raw key down, raw key repeat. If you're just trying to get keyboard data, you can get the Mac character codes and you get the modifiers that go with those. Really simple. So you can just get the direct character codes out of there, or you can get the virtual key if you want to get the virtual key instead. So, What are we going to do next? We'll do the Rocket Demo. Some of you may have seen this before. And what I'll do, we're just going to open up the, just going to run the Rocket Demo.
I think I have it set up for the iShock. Yes, so I have the iShock set up for the Rocket demo. And you can see it's very simple. Using the joystick for the input, using the button for the thrust. I have zoom in and zoom out on this. It's one call. Very, very simple.
[Transcript missing]
I think this is set up for a different device. I can't remember what I set this up for. I set this up for the joystick. So this is just a little little test demo that's really easy to see. You can see that it's a joystick set up thrust and fire on the joystick, easy to retrieve and what this is, this stuff is set up using some of the sample code and basically all you are doing is getting, I have a wrap around, get element value and you pass it, a pointer to the element, a pointer to the device you have and it will find the value for it or you can queue it and get the next event out of the queue. So this is, again, one call to get the element value out of it.
You call build device list, you are going to build the device list, you find the device you are interested in, you have one call to get the element value so you don't have to do all that code I showed you. It's not real, it's already been done for you and hopefully you can use that. So we will go back to the slides and we will talk about Gideon saving configuration and I will show you, I will do a little bit more demo at the end.
So one of the things we need to talk about is how you configure. I mean, yeah, you can get lots of, I can get the first button on a device, but is that what the user wants to use for fire? I don't know. Maybe. That joystick is set up for as a trigger, but some of the other ones, the eye shock for example, the buttons one through four are set up on the hat over here.
I'm sure if you're using this joystick as your control here, I probably want to use something on this side of the gamepad. So in that case, you want to do configuration. You want to let the user decide what they want to configure, and you want to make it as easy as possible.
So first thing, the methodology behind it is we have a set of actions in the application. You may want to turn left, turn right, thrust, fire, zoom in, zoom out kind of thing. Those are the kind of actions. And you want to map those to elements. So we're going to have a UI that you present, whatever you want, whether it's immersive or whether it's just Aqua.
It took about 50 lines of code to put a UI together that I'll show you in a minute, so it's not a real big deal. And then you call a function that basically goes out and pulls all the devices. We wrote a function that basically looks at current device values. It then pulls all the devices to find out what changed, and if it's beyond a threshold, it reports you back that device.
If it times out, it returns timeout with no, or that's the default. If it times out, it returns timeout with no, or that device in element. Timeout returns without anything, returns null, so you know that the user either kind of canceled out or waited for the thing to cancel itself out.
When the user selects something to assign, we do exactly what I said. We build those queues, pull across the queues, and return what's associated. We use cookie and device interface to pull for the input or to build a queue for the input. So it's real simple. Once you get that information back out, you can find the cookie, you find the device interface, and you got it. You got the input right there. You associate that with your action, and you're done.
When we talk about saving configurations, we need to bring up my other slide here, which tells me exactly what we're going to save. And the reason I want to do this is because how do you identify a device across multiple systems of disparate users that you may have, you know, a gamepad here that's close to your other gamepad, but it's not quite the other gamepad.
So what do you want to do? First thing you want to do is for the element you want to save the cookie. So if you find that same device, you have the cookie to it, you know exactly where it is, it's on the same device, you pull the cookie out.
You also want to save the usage and usage page. Let's say it was an x-axis, but you can't find that, the iShock device, but you do find the USB gamepad pro. If you know you're looking for an x-axis, it doesn't matter what the cookie was, because the cookie won't apply to a different device, a different product, vendor ID, but you can look for an x-axis and assign it to the x-axis. Very simple to do.
As far as devices, you want to save the device serial number. Most devices don't have it, but if you do have it, it's really an easy way to determine with a product and vendor ID exactly the same device. You can track that device exactly wherever the person plugs it in, whatever system it's on. Whether they have 10 of those devices input in their system, you'll find that exact device. So if they want to use their purple painted joystick, they can use their purple painted joystick every time because you'll be able to find it.
Vendor ID, Product ID, and Location. Those three together basically specify a certain device that's plugged into a certain place in the system. So we're assuming that someone doesn't get lots of devices and change them around all the time, that'll probably find the same joystick, gamepad, whatever the person's been using and will continue to use it.
And lastly, usage page and usage. If you can't match anything else, better to find a gamepad that's close or a joystick that's close to a gamepad and try and match that than to find the jog shuttle to let them try and play their gamepad. shoot them up or quake with their jog shuttle.
So again, I think we just went through these. Top to bottom, vendor, product ID, serial number, and cookie. If you got all those, that identifies a specific element on a specific device. You're in there, you're golden. If you can't get the serial number, you can substitute it in the location in the USB chain. That says if the thing's plugged into the same place, it's great.
Vendor ID, Product ID, and Cookie. So this says, I don't know if it's the same device, but I know it's the same type of device plugged into the system. And when I say type, I mean exactly the same make and model spec from the same manufacturer. So I know that that is, unless they have two of them, it's probably the same device.
Then usage and usage page. If you find a usage and usage page match for your device, you want to make sure you realize that the cookie is no longer valid. Actually, the cookie is no longer valid. I'm sorry, that's correct. Once you get off the vendor and product ID, your cookie will no longer be valid. So you then have to use the usage and usage page for the element. Search down and try and find the x-axis or the first button or the fifth button.
And any device, if you can't match any of those, so you haven't found the, you know, you have a gamepad and you didn't find any gamepads but you have a joystick, again, search by usage and usage page. And that should find exactly what you're looking for, the closest match. And I think we'll-- Bring up the demo machine again. It just happens that I have an example of this.
So here's my configuration window. Pretty simple. Mac ally USB joystick, which is what we're seeing here. Real simple with Carbon vents, by the way, to run both at the same time, so I can reconfigure this on the fly. I want to switch to this device. Remember, all these are plugged into the same computer. I used to do x-axis, iShock x-axis. So I just scanned all of them, no problem, found that one. User wants to use the iShock instead. For thrust, I want to use that button.
And for fire, I want to use the jogging shuttle button 5 for fire. So no problem with the mixed devices real easily. And now if you notice, it's reconfigured automatically. Isn't that great? No problems. While everything's working, I didn't have to rebuild a list or do anything like that.
Reconfigured thrust is over here. And no hands on this thing. No hands. And the fire is down here on your jogging shuttle. So that works great. Another thing you can do, though, is let's... Let's go to something more normal. So we have button 5 and 6. Actually, yeah, I'll leave it at that. So we have x-axis, y-axis on this device, button 5 and 6. I'm going to hit OK there. And I'm going to save that configuration. I save it to a flat file. I saved exactly the information I showed you to a flat file. I'm going to quit this.
Reopen it. It loads the configuration, so we'll look at the configuration, and again, it's the same configuration we had before. And to prove it that we're actually changing something, I'll actually make that... "Button 6, I don't want to do that." You can do that, it's not a problem. There's no reason you can't map it that way. We'll make it button 7. Hit OK. Again, we'll save the configuration and we'll quit. Now we should have button 6 and 7 here.
configuration for the input button six and seven. So that's the input we want from this thing. So we're playing our game, we're having a great time. Doing great things. Having a blast. This is very exciting. Oh, here's something interesting. I will show you something interesting. Okay, I'm moving... This is the hat... This is the little joystick hat on this device.
Just so you see, it goes all the way to that corner. This is even with calibrated input. This is with recalibrating to the full range. Goes all the way up there. Now I'm going to the top left and bringing it across. I'm still bringing it across. Still bringing it across.
And if you notice, it doesn't have the full range. And that's the full motion from that hat switch. So understand when you're designing games that users may have devices that don't quite respond to every single corner of the input and that that may be something that you don't want to have to have the user turn at a certain rate to make a corner and their device doesn't. It would be real frustrating if the device can't make that corner. So we'll quit this again.
and I'm going to go over to Bob's house. And Bob didn't spend the extra money and he only ended up with the GamePad Pro. So I want to play the game anyway. So we're going to launch and or actually Bob brought his GamePad Pro over, or Bob borrowed mine, so now I have to use this because I don't have it anymore. Oh, well that's pretty nice. It found this as a GamePad Pro and said, I know how to configure that, I'll configure X and Y axes, buttons 6 and 7, and so "Play a little game.
Obviously, Thrust and Fire, we happen to be somewhere here and here. Not the best buttons, but it did configure it, found the device, and actually mapped it correctly." So, since we saved the configuration, it'll keep this as a saved configuration mapping whatever it can until I actually save a different configuration. At that point, it would save a different configuration. One thing to look at also, which we'll quit that again and I'll remove the GamePad Pro because now all I have is the joystick.
If I bring up the configuration for the joystick, one thing we can see is that it mapped the X and Y axes, but this only has four buttons, so it doesn't know how to map those extra buttons. You could easily expand the code and say, hey, if it's not, if we're past the end of the buttons, I'll pick the first buttons, whatever you want it to do. The idea here is to give the user something that's pretty close to work with so they're not actually, you know, suffering from a problem with not having to reconfigure an entire device.
You can reconfigure a lot of inputs for your application every time they play because they change devices. So what else do I got to show you? So we saw the config save, we saw the different devices. There's no, except for location ID, which identifies a computer, there's no specific thing in your list of savings, of things you save that identifies that you have to go, bring it back to the slides please, you actually have to go to, let me skip one.
You have to be on that one computer. So you could bring an input file, a configuration file, on your computer, your development machine, and what you're going to find is you're never going to find that location ID for your development machine. But what you will find is the vendor ID and the product ID, and so you'll be able to identify if you put a configuration in for a certain type of joystick or you put a gamepad configuration in, you'll definitely identify that there is that type of thing on the system. So, Roadmap 136, Sound and Networking for Games is 2 o'clock in the same room.
I'd like to see you all there. And this afternoon, or this evening, basically at 5, last chance to vent your interest, what we did good, what we did bad about games and Mac OS. So we'd like to see you there also to hear about anything that you think that we should be adding to the OS or how we should be improving it.
That's my information. Please, if you have questions, feel free to contact me. The sample code again will be up, so I think it will handle almost everything for you. The code that you're actually, to do the interface, to do the polling, it's one call to configure inputs, it's one call to build a device list, one call to release it. It's not real complicated. Then you can look at the code and you can learn from that exactly how to implement stuff.