Guide

Introduction

This guide will show you how you can integrate and use the HIDRemote class in your application within minutes.

Preparations

Download the HIDRemote class' sourcecode, unarchive it and read the license carefully. If you agree with the license, copy the files HIDRemote.m and HIDRemote.h into your project's folder and proceed.

Adding HIDRemote to your Xcode project

Perform these steps to include the HIDRemote class into your application's Xcode project:

  1. Drag HIDRemote.m und HIDRemote.h into the "Groups & Files" listview (located on the left of your Xcode project window). Xcode will ask you which target you want to add the files to. Choose the correct target and click "Add".
  2. In the "Groups & Files" listview, navigate to "Targets" > [target for your application] > Link Binary With Libraries
  3. There, verify that your target is linked against Cocoa.framework and IOKit.framework. If your project targets OS X releases older than OS X 10.10, Carbon.framework is needed as well. Add missing frameworks by right clicking on "Link Binary With Libraries" and selecting "Add > Existing Frameworks" from the popup.
  4. Build your project and verify that the linker doesn't complain about unresolved symbols.

Using HIDRemote in your application

You're now ready to use HIDRemote in your application. First, implement the required delegate method in which you'll handle the remote control events:

#import "HIDRemote.h" // .. - (void)hidRemote:(HIDRemote *)hidRemote eventWithButton:(HIDRemoteButtonCode)buttonCode isPressed:(BOOL)isPressed fromHardwareWithAttributes:(NSMutableDictionary *)attributes { NSLog(@"%@: Button with code %d %@", hidRemote, buttonCode, (isPressed ? @"pressed" : @"released")); }

That delegate method will be called later whenever the button was pressed or released.

Next, determine which mode you'll need HIDRemote to operate in. The reference documentation for startRemoteControl contains a complete list with explainations of each mode. We'll be using the recommended value, kHIDRemoteModeExclusiveAuto in this example.

Now you can use +isCandelairInstallationRequiredForRemoteMode: to check whether the operating system version your application is running under makes the installation of Candelair necessary in order to get access to the remote in the desired mode:

if ([HIDRemote isCandelairInstallationRequiredForRemoteMode:kHIDRemoteModeExclusiveAuto]) { // Candelair needs to be installed. Inform the user about it. } else { // Start using HIDRemote .. }

If the compatibility check method returns NO, Candelair is either already installed or not necessary for the OS release in use. In that case, you can go ahead and start using HIDRemote:

// Start using HIDRemote .. [[HIDRemote sharedHIDRemote] setDelegate:self]; if ([[HIDRemote sharedHIDRemote] startRemoteControl:kHIDRemoteModeExclusiveAuto]) { // Start successful } else { // Start failed }

That's it! HIDRemote is started and will be sending messages for incoming button presses to your delegate.

Optimizing remote control support in your application

Your instance of the HIDRemote class informs other applications running at the same time about its status. This is used to implement the Exclusive Lock Lending feature and to provide best integration with drivers and remote control solutions.

Let's take a look at the latter case and why it'll improve the user experience for the users of your software when you use it. Let's assume that your application uses the Menu button for navigation of its interface, but that it doesn't need to know about long presses of the Menu button. We let our instance of HIDRemote (which is the shared instance in this example) know before invoking -startRemoteControl:.

[[HIDRemote sharedHIDRemote] setUnusedButtonCodes:[NSArray arrayWithObjects: [NSNumber numberWithInt:(int)kHIDRemoteButtonCodeMenuHold], nil] ];

This information can now be used by advanced remote control solutions such as Remote Buddy to forward all button presses directly to your application while opening its own menu on a long press of the Menu button.

Likewise, drivers can use this information to determine that it doesn't need to emulate a MenuHold button code for it. Instead of waiting until it can know whether the menu button press was short or long and in consequence issue a Menu or MenuHold button event, the driver could directly issue a Menu button press event the moment the Menu button was pressed. Such an end-to-end optimization is only possible when applications provide the necessary metadata. Our experience with Remote Buddy, which performs this kind of optimizations for years now, has shown that this is not just a technical detail, but that users actually notice and appreciate the reduced reaction times this metadata allows.

The use of -setUnusedButtonCodes: is optional, but we strongly recommend making use of it to deliver a noticably better user experience.

Launchers and other background applications

If you're writing an application that operates in the background and needs an exclusive lock, please consider to enable Exclusive Lock Lending in your application, so that other applications can at least temporarily lend your exclusive lock. Enabling exclusive lock lending is as easy as making a call to the -setExclusiveLockLendingEnabled: method of your instance.

[[HIDRemote sharedHIDRemote] setExclusiveLockLendingEnabled:YES];

This one line is all that's needed. But if needed, HIDRemote provides you with finer-grained control over the Exclusive Lock Lending feature. If, for example, you'd like to only lend your application's exclusive lock to applications that need a shared lock or operate in exclusive-auto mode (both of which are more or less guaranteed to return the lock to you at some point in time), you could implement a -hidRemote:lendExclusiveLockToApplicationWithInfo: delegate method like this:

- (BOOL)hidRemote:(HIDRemote *)hidRemote lendExclusiveLockToApplicationWithInfo:(NSDictionary *)applicationInfo { NSNumber *remoteModeNumber; if ((remoteModeNumber = [applicationInfo objectForKey:kHIDRemoteDNStatusModeKey]) != nil) { switch ((HIDRemoteMode)[remoteModeNumber intValue]) { // Lend exclusive lock to all applications operating in shared or // exclusive-auto mode case kHIDRemoteModeShared: case kHIDRemoteModeExclusiveAuto: return (YES); break; } } // Don't lend the lock to applications operating in other modes return (NO); }

Compatibility with third-party HID drivers

HIDRemote implements a powerful matching routine for IOKit HID remote control devices that doesn't rely solely on particular class names. This makes HIDRemote open for third-party HID remote control drivers out-of-the-box. All an IOKit driver's instance needs to do (apart from emulating an Apple® Remote) is to set a value of 1 for its CandelairHIDRemoteCompatibilityMask property.

bool MyAIREmuClass::init(OSDictionary *dict) { if (!super::init(dict)) { return(false); } // .. this->setProperty("CandelairHIDRemoteCompatibilityMask",(unsigned long long)1, (unsigned int)64); // .. return (true); }

Example source code

The following is sample sourcecode for a delegate that you can use as a template for your own projects.

MyClass.h

// // MyClass.h // MyApplication // #import <Cocoa/Cocoa.h> #import "HIDRemote.h" @interface MyClass : NSObject <HIDRemoteDelegate> { // .. } #pragma mark -- Start and stop remote control support -- - (BOOL)startRemoteControl; - (void)stopRemoteControl; // ..

MyClass.m

// // MyClass.m // MyApplication // #import <MyClass.h> @implementation MyClass // .. - (id)init { if (self = [super init]) { // .. // In this example, our application doesn't need the MenuHold button code. Share // this information with everybody else [[HIDRemote sharedHIDRemote] setUnusedButtonCodes:[NSArray arrayWithObjects: [NSNumber numberWithInt:(int)kHIDRemoteButtonCodeMenuHold], nil] ]; // .. } return (self); } - (void)dealloc { // Our delegate class is deallocated. Stop the remote control and make sure HIDRemote // will no longer make calls to our delegate instance. [[HIDRemote sharedHIDRemote] stopRemoteControl]; [[HIDRemote sharedHIDRemote] setDelegate:nil]; // .. [super dealloc]; } #pragma mark -- Start and stop remote control support -- - (BOOL)startRemoteControl { HIDRemoteMode remoteMode; HIDRemote *hidRemote; hidRemote = [HIDRemote sharedHIDRemote]; remoteMode = kHIDRemoteModeExclusiveAuto; // Check whether the installation of Candelair is required to reliably operate in this mode if ([HIDRemote isCandelairInstallationRequiredForRemoteMode:remoteMode]) { // Reliable usage of the remote in this mode under this operating system version // requires the Candelair driver to be installed. Let's inform the user about it. NSAlert *alert; if ((alert = [NSAlert alertWithMessageText:NSLocalizedString(@"Candelair driver installation necessary", @"") defaultButton:NSLocalizedString(@"Download", @"") alternateButton:NSLocalizedString(@"More information", @"") otherButton:NSLocalizedString(@"Cancel", @"") informativeTextWithFormat:NSLocalizedString(@"An additional driver needs to be installed before %@ can reliably access the remote under the OS version installed on your computer.", @""), [[NSBundle mainBundle] objectForInfoDictionaryKey:(id)kCFBundleNameKey]]) != nil) { switch ([alert runModal]) { case NSAlertDefaultReturn: [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:@"http://www.candelair.com/download/"]]; break; case NSAlertAlternateReturn: [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:@"http://www.candelair.com/"]]; break; } } } else { // Candelair is either already installed or not required under this OS release => proceed! if (remoteMode == kHIDRemoteModeExclusive) { // When used in exclusive, non-auto mode, enable exclusive lock lending. This isn't required // but there are good reasons to do this. [hidRemote setExclusiveLockLendingEnabled:YES]; } // Start remote control if ([hidRemote startRemoteControl:remoteMode]) { // Start was successful NSLog(@"HIDRemote started successfully"); return (YES); } else { // Start failed NSLog(@"Couldn't start HIDRemote"); } } return (NO); } - (void)stopRemoteControl { [[HIDRemote sharedHIDRemote] stopRemoteControl]; } #pragma mark -- Handle remote control events -- - (void)hidRemote:(HIDRemote *)hidRemote eventWithButton:(HIDRemoteButtonCode)buttonCode isPressed:(BOOL)isPressed fromHardwareWithAttributes:(NSMutableDictionary *)attributes { NSString *buttonName = nil; switch (buttonCode) { case kHIDRemoteButtonCodeUp: buttonName = @"Up"; break; case kHIDRemoteButtonCodeUpHold: buttonName = @"Up (hold)"; break; case kHIDRemoteButtonCodeDown: buttonName = @"Down"; break; case kHIDRemoteButtonCodeDownHold: buttonName = @"Down (hold)"; break; case kHIDRemoteButtonCodeLeft: buttonName = @"Left"; break; case kHIDRemoteButtonCodeLeftHold: buttonName = @"Left (hold)"; break; case kHIDRemoteButtonCodeRight: buttonName = @"Right"; break; case kHIDRemoteButtonCodeRightHold: buttonName = @"Right (hold)"; break; case kHIDRemoteButtonCodeCenter: buttonName = @"Center"; break; case kHIDRemoteButtonCodeCenterHold: buttonName = @"Center (hold)"; break; case kHIDRemoteButtonCodeMenu: buttonName = @"Menu"; break; case kHIDRemoteButtonCodeMenuHold: buttonName = @"Menu (hold)"; break; case kHIDRemoteButtonCodePlay: buttonName = @"Play (Alu Remote!)"; break; case kHIDRemoteButtonCodePlayHold: buttonName = @"Play (Alu Remote!) (hold)"; break; } if (isPressed) { NSLog(@"Button "%@\ was pressed", buttonName); } else { NSLog(@"Button "%@\ was released", buttonName); } } // .. @end