Apple Infrared Remote Daemon
© Amit Singh. All Rights Reserved. Written in January 2006Introduction
Certain Apple computers, such as the x86-based iMac and the MacBook Pro, ship with an infrared remote control that is used to control the Front Row media application. iremoted is a command-line program that listens for button-press events and prints the identifier (the HID element cookie, to be precise) of the button in question.
$ ./iremoted
0x16 pressed
0x16 released
0x17 pressed
0x17 released
...
Note that only the plus and minus (volume) buttons have press and release events that are reported independently. For the other buttons, iremoted will report a press/release pair only after the button is released.
Details
iremoted was originally tested on an x86-based 17-inch iMac. The built-in IR controller in this machine is a USB-based human interface device (HID). The usage table information for this device includes usages in the Generic Desktop Page (page ID 0x0), the Generic Device Controls Page (page ID 0x06), the Consumer Page (page ID 0x0C), and a vendor-defined page (page ID 0xff01).
If you are not familiar with HID usage tables and the associated terminology, please refer to the documentation available on USB.org. In particular, refer to the document "HID Usage Tables". This discussions also assumes familiarity with HID programming on Mac OS X.
- Generic Desktop — includes one-shot controls (OSC) with usage IDs 0x86 (System App Menu), 0x89 (System Menu Select), 0x8a (System Menu Right), 0x8b (System Menu Left), 0x8c (System Menu Up), and 0x8d (System Menu Down).
- Generic Device Controls — includes a dynamic value with the usage ID ID 0x22 (Wireless ID), which identifies a wireless device in a wireless subsystem.
- Consumer — includes on/off controls with the usage IDs 0x40 (Menu), 0xb3 (Fast Forward), and 0xb4 (Rewind).
- Reserved — includes usage IDs 0x20, 0x21, 0x22, and 0x23.
There are multiple ways in which to programmatically obtain remote control button-press information from the IR controller. At the lowest level, one could write a device driver. At a much higher level, one could depend on another program (such as iremoted) to obtain that information. iremoted itself uses a middle-ground approach: it communicates with Apple's in-kernel IR driver, which is implemented by the AppleIRController.kext kernel extension. Given the I/O Kit's power and flexibility, especially in allowing user-level access to hardware, this is a very reasonable approach.
iremoted first looks up the registered IOService object that corresponds to the IR controller. It then gets a plug-in interface for for the HID family's user client (kIOHIDDeviceUserClientTypeID). Once it has the interface, it calls its QueryInterface() method, which yields an IOHIDDeviceInterface on success. The returned pointer can be cast to an instance of the IOHIDDeviceInterface122 class, which represents a specific version (1.2.2) of the primary interface to HID devices.
Armed with the HID device interface, iremoted calls its copyMatchingElements() method to examine and record the values of the "cookies" that represent HID elements, for example:
$ ioreg -l -w 0 # output artificially wrapped
...
| +-o AppleIRController <class AppleIRController, registered,
matched, active, busy 0, retain count 10>
...
{
# this is "System App Menu"
"Size"=1,"UnitExponent"=0,"ReportSize"=1,"ScaledMin"=0,
"ScaledMax"=1,"ElementCookie"=7,"Max"=1,"Unit"=0,
"IsArray"=Yes,"HasNullState"=Yes,"IsRelative"=Yes,
"Report ID"=0,"ValueLocation"=316,"Min"=0,"IsWrapping"=No,
"UsagePage"=1,"HasPreferredState"=Yes,"ReportCount"=1,
"IsNonLinear"=No,"Usage"=134,"Type"=2
}
...
iremoted then opens the HID interface and performs the following key operations:
- Allocates an event queue (an instance of
IOHIDQueueInterface) using the interface'sallocQueue()method. - Creates the queue using the queue's
create()method. - Calls the queue's
addElement()method to add each element of interest to the queue. In this case, the elements correspond to the six buttons of the Apple Remote Control. - Creats an asynchronous event source using the queue's
createAsyncEventSource()method. - Calls the queue's
setEventCallOut()method, which invokes a given callback function when the queue transitions to non-empty. - Adds the newly created event source to the current thread's run loop.
- Runs the run loop.
Thereafter, pressing a button on the Apple Remote Control will cause the callback function to be invoked. The callback uses the queue's getNextEvent() method to read the next event from the queue. Each event is an IOHIDEventStruct, whose elementCookie field is the element of interest — that is, the button that was pressed. The value field, depending on whether it is zero or not, determines if the button was pressed or released.
For a better understanding of how IOHIDQueueInterface functions work, please refer to <IOHIDLib/IOHIDLib.h> in the source for the I/O Kit's HID family (the IOHIDFamily Darwin package).
Controlling Keynote Presentations
To illustrate the remote's utility, iremoted supports translating forward and backward button presses to "next slide" and "previous slide" events in a running instance of Apple's Keynote application.
To use this functionality of iremoted, run it with the -k option. Thereafter, with a Keynote presentation playing, the remote can be used to control slide transition.
$ ./iremoted -k
# now play a Keynote presentation
# use the remote's |<< and >>| buttons
Source
iremoted.c