My first kernel extension for Logitech presenter device

Posted on

I do a lot of presentation particularly those class projects. To make my presentation looks more professional, I decide to buy Logitech Professional Presenter R800. It is very nice. I recommend you to have one if you have to give a lot of presentation.

However, what bothers me is that not all buttons work with Apple Keynote, particularly Play button and Blank button. Well, the device is designed for Microsoft PowerPoint on Windows.

The questions are why it doesn't work and how to make it works with Keynote?

How does the device work?

Actually, that device is merely a wireless keyboard with only 4 keys including, Page Up, Page Down, F5 and . (dot). Page Up key and Page Down key are for moving slides back and forth. F5 is for starting a presentation. Dot key is for blanking.

PowerPoint conforms to all those keys but Keynote doesn't. Keynote uses, by default, CMD + ALT + P to start playing a slide and B for blanking the slide. Dot key for Keynote is for terminating the slideshow which may or may not what you want.

What normal people would do

The simple and sane way is to modify the shortcut key. You can go to System Preference > Keyboard > Keyboard Shortcuts > Application Shortcuts, add Keynote application and overide Play Slideshow to use F5. However, This approach doesn't work if you want to modify the Blank key.

You can ignore Blank key actually if you want it to stop playing the slide but I want to blank the screen. To me, stop playing to the slide means the presentation is end and I will be at the computer to stop the slideshow manually.

What I actually do

To satify my geek spirit, I write my own kernel extension for the device.

The first question is where should I start? The answer is ioreg. I know that the device is a keyboard. Keyboard is a kind of Human Interface Device (HID). Apple Keyboard is also a keyboard. Therefore, I should take a look at driver which is used with Apple Keyboard.

Before I go, I should mention a little bit about IOKit Framework first. IOKit Framework is a framework that Apple provides to developers for writing a device driver in an OOP way wtih C++.

The belowing snippet shows the partial output from ioreg:

...
| |   |   +-o Apple Internal Keyboard / Trackpad@4600000  <class IOUSBDevice, id 0x100000292, registered, matched, active, busy 0 (1173 ms), r$
    | |   |     +-o IOUSBCompositeDriver  <class IOUSBCompositeDriver, id 0x100000295, !registered, !matched, active, busy 0, retain 4>
    | |   |     +-o Apple Internal Keyboard@0  <class IOUSBInterface, id 0x100000296, registered, matched, active, busy 0 (213 ms), retain 9>
    | |   |     | +-o AppleUSBTCKeyboard  <class AppleUSBTCKeyboard, id 0x10000029a, registered, matched, active, busy 0 (33 ms), retain 12>
    | |   |     |   +-o IOHIDInterface  <class IOHIDInterface, id 0x10000029f, registered, matched, active, busy 0 (31 ms), retain 7>
    | |   |     |   | +-o AppleEmbeddedKeyboard  <class AppleEmbeddedKeyboard, id 0x1000002a0, registered, matched, active, busy 0 (0 ms), retain $
    | |   |     |   |   +-o IOHIDKeyboard  <class IOHIDKeyboard, id 0x1000002a2, registered, matched, active, busy 0 (0 ms), retain 8>
    | |   |     |   |   | +-o IOHIDSystem  <class IOHIDSystem, id 0x1000002ac, registered, matched, active, busy 0 (0 ms), retain 20>
    | |   |     |   |   |   +-o IOHIDStackShotUserClient  <class IOHIDStackShotUserClient, id 0x10000035d, !registered, !matched, active, busy 0, $
    | |   |     |   |   |   +-o IOHIDUserClient  <class IOHIDUserClient, id 0x100000371, !registered, !matched, active, busy 0, retain 5>
    | |   |     |   |   |   +-o IOHIDParamUserClient  <class IOHIDParamUserClient, id 0x100000385, !registered, !matched, active, busy 0, retain 5$
    | |   |     |   |   |   +-o IOHIDEventSystemUserClient  <class IOHIDEventSystemUserClient, id 0x1000003c1, !registered, !matched, active, busy$
    | |   |     |   |   |   +-o IOHIDEventSystemUserClient  <class IOHIDEventSystemUserClient, id 0x1000003c9, !registered, !matched, active, busy$
    | |   |     |   |   +-o IOHIDConsumer  <class IOHIDConsumer, id 0x1000002a3, registered, matched, active, busy 0 (0 ms), retain 8>
    | |   |     |   |   | +-o IOHIDSystem  <class IOHIDSystem, id 0x1000002ac, registered, matched, active, busy 0 (0 ms), retain 20>
    | |   |     |   |   |   +-o IOHIDStackShotUserClient  <class IOHIDStackShotUserClient, id 0x10000035d, !registered, !matched, active, busy 0, $
    | |   |     |   |   |   +-o IOHIDUserClient  <class IOHIDUserClient, id 0x100000371, !registered, !matched, active, busy 0, retain 5>
    | |   |     |   |   |   +-o IOHIDParamUserClient  <class IOHIDParamUserClient, id 0x100000385, !registered, !matched, active, busy 0, retain 5$
    | |   |     |   |   |   +-o IOHIDEventSystemUserClient  <class IOHIDEventSystemUserClient, id 0x1000003c1, !registered, !matched, active, busy$
    | |   |     |   |   |   +-o IOHIDEventSystemUserClient  <class IOHIDEventSystemUserClient, id 0x1000003c9, !registered, !matched, active, busy$
    | |   |     |   |   +-o IOHIDSystem  <class IOHIDSystem, id 0x1000002ac, registered, matched, active, busy 0 (0 ms), retain 19>
    | |   |     |   |     +-o IOHIDStackShotUserClient  <class IOHIDStackShotUserClient, id 0x10000035d, !registered, !matched, active, busy 0, re$
    | |   |     |   |     +-o IOHIDUserClient  <class IOHIDUserClient, id 0x100000371, !registered, !matched, active, busy 0, retain 5>
    | |   |     |   |     +-o IOHIDParamUserClient  <class IOHIDParamUserClient, id 0x100000385, !registered, !matched, active, busy 0, retain 5>
    | |   |     |   |     +-o IOHIDEventSystemUserClient  <class IOHIDEventSystemUserClient, id 0x1000003c1, !registered, !matched, active, busy 0$
    | |   |     |   |     +-o IOHIDEventSystemUserClient  <class IOHIDEventSystemUserClient, id 0x1000003c9, !registered, !matched, active, busy 0$
    | |   |     |   +-o IOHIDLibUserClient  <class IOHIDLibUserClient, id 0x1000003c7, !registered, !matched, active, busy 0, retain 6>
    | |   |     |   +-o IOHIDLibUserClient  <class IOHIDLibUserClient, id 0x10000044b, !registered, !matched, active, busy 0, retain 6>
...

What does this information tell us? It shows the hierarchy of drivers which implys us the chain of command. For example, IOHIDInterface is said to be a provider of AppleEmbeddedKeyboard; you can think of it as a data provider.

The class beginning with IO belongs to the system. As you can see, there are 2 custom drivers, AppleUSBTCKeyboard and AppleEmbeddedKeyboard. I investigate further by the command ioreg -c and find out that AppleUSBTCKeyboard is a subclass of IOUSBHIDDriver and AppleEmbeddedKeyboard is a subclass of IOHIDEventDriver.

My initial assumption about the driver is that it should do something about event dispatching. I start by looking at IOUSBHIDDriver.h and IOHIDEventDriver.h and found nothing that seems to match my assumption. Is my assumption wrong?

I google ‘AppleEmbeddedKeyboard’ and find a source code from Apple open source repository. I take a look at AppleEmbeddedKeyboard.cpp find this method overiding interesting:

void AppleEmbeddedKeyboard::dispatchKeyboardEvent(
                                AbsoluteTime                timeStamp,
                                UInt32                      usagePage,
                                UInt32                      usage,
                                UInt32                      value,
                                IOOptionBits                options)
{    
    ...
}

It turns out that the method dispatchKeyboardEvent() is declared in IOHIDEventService.h. So, I decide to write the kernel extension based on IOHIDEventDriver. With Apple documentation and suggestion from Pavel Prokofiev who writes macosx-nosleep-extension, I am able to complete it (It is not that hard but takes time to understand what things are).

What my code does is intercepting the key and replace it if necessary.

You can see my source code here https://github.com/ake-koomsin/LogitechWirelessPresenterKext.