summaryrefslogtreecommitdiffstats
path: root/osdep/ar
diff options
context:
space:
mode:
Diffstat (limited to 'osdep/ar')
-rw-r--r--osdep/ar/HIDRemote.h378
-rw-r--r--osdep/ar/HIDRemote.m2068
2 files changed, 2446 insertions, 0 deletions
diff --git a/osdep/ar/HIDRemote.h b/osdep/ar/HIDRemote.h
new file mode 100644
index 0000000000..9dd16faa5a
--- /dev/null
+++ b/osdep/ar/HIDRemote.h
@@ -0,0 +1,378 @@
+//
+// HIDRemote.h
+// HIDRemote V1.2
+//
+// Created by Felix Schwarz on 06.04.07.
+// Copyright 2007-2011 IOSPIRIT GmbH. All rights reserved.
+//
+// The latest version of this class is available at
+// http://www.iospirit.com/developers/hidremote/
+//
+// ** LICENSE *************************************************************************
+//
+// Copyright (c) 2007-2011 IOSPIRIT GmbH (http://www.iospirit.com/)
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without modification,
+// are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice, this list
+// of conditions and the following disclaimer.
+//
+// * Redistributions in binary form must reproduce the above copyright notice, this
+// list of conditions and the following disclaimer in the documentation and/or other
+// materials provided with the distribution.
+//
+// * Neither the name of IOSPIRIT GmbH nor the names of its contributors may be used to
+// endorse or promote products derived from this software without specific prior
+// written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
+// SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+// TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+// BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+// DAMAGE.
+//
+// ************************************************************************************
+
+
+// ************************************************************************************
+// ********************************** DOCUMENTATION ***********************************
+// ************************************************************************************
+//
+// - a reference is available at http://www.iospirit.com/developers/hidremote/reference/
+// - for a guide, please see http://www.iospirit.com/developers/hidremote/guide/
+//
+// ************************************************************************************
+
+
+#import <Cocoa/Cocoa.h>
+
+#include <Carbon/Carbon.h>
+
+#include <unistd.h>
+#include <mach/mach.h>
+#include <sys/types.h>
+
+#include <IOKit/IOKitLib.h>
+#include <IOKit/IOCFPlugIn.h>
+#include <IOKit/IOMessage.h>
+#include <IOKit/hid/IOHIDKeys.h>
+#include <IOKit/hid/IOHIDLib.h>
+#include <IOKit/hid/IOHIDUsageTables.h>
+#include <IOKit/hidsystem/IOHIDLib.h>
+#include <IOKit/hidsystem/IOHIDParameter.h>
+#include <IOKit/hidsystem/IOHIDShared.h>
+
+#pragma mark -- Enums / Codes --
+
+typedef enum
+{
+ kHIDRemoteModeNone = 0L,
+ kHIDRemoteModeShared, // Share the remote with others - let's you listen to the remote control events as long as noone has an exclusive lock on it
+ // (RECOMMENDED ONLY FOR SPECIAL PURPOSES)
+
+ kHIDRemoteModeExclusive, // Try to acquire an exclusive lock on the remote (NOT RECOMMENDED)
+
+ kHIDRemoteModeExclusiveAuto // Try to acquire an exclusive lock on the remote whenever the application has focus. Temporarily release control over the
+ // remote when another application has focus (RECOMMENDED)
+} HIDRemoteMode;
+
+typedef enum
+{
+ /* A code reserved for "no button" (needed for tracking) */
+ kHIDRemoteButtonCodeNone = 0L,
+
+ /* Standard codes - available for white plastic and aluminum remote */
+ kHIDRemoteButtonCodeUp,
+ kHIDRemoteButtonCodeDown,
+ kHIDRemoteButtonCodeLeft,
+ kHIDRemoteButtonCodeRight,
+ kHIDRemoteButtonCodeCenter,
+ kHIDRemoteButtonCodeMenu,
+
+ /* Extra codes - Only available for the new aluminum version of the remote */
+ kHIDRemoteButtonCodePlay,
+
+ /* Masks */
+ kHIDRemoteButtonCodeCodeMask = 0xFFL,
+ kHIDRemoteButtonCodeHoldMask = (1L << 16L),
+ kHIDRemoteButtonCodeSpecialMask = (1L << 17L),
+ kHIDRemoteButtonCodeAluminumMask = (1L << 21L), // PRIVATE - only used internally
+
+ /* Hold button standard codes - available for white plastic and aluminum remote */
+ kHIDRemoteButtonCodeUpHold = (kHIDRemoteButtonCodeHoldMask|kHIDRemoteButtonCodeUp),
+ kHIDRemoteButtonCodeDownHold = (kHIDRemoteButtonCodeHoldMask|kHIDRemoteButtonCodeDown),
+ kHIDRemoteButtonCodeLeftHold = (kHIDRemoteButtonCodeHoldMask|kHIDRemoteButtonCodeLeft),
+ kHIDRemoteButtonCodeRightHold = (kHIDRemoteButtonCodeHoldMask|kHIDRemoteButtonCodeRight),
+ kHIDRemoteButtonCodeCenterHold = (kHIDRemoteButtonCodeHoldMask|kHIDRemoteButtonCodeCenter),
+ kHIDRemoteButtonCodeMenuHold = (kHIDRemoteButtonCodeHoldMask|kHIDRemoteButtonCodeMenu),
+
+ /* Hold button extra codes - Only available for aluminum version of the remote */
+ kHIDRemoteButtonCodePlayHold = (kHIDRemoteButtonCodeHoldMask|kHIDRemoteButtonCodePlay),
+
+ /* DEPRECATED codes - compatibility with HIDRemote 1.0 */
+ kHIDRemoteButtonCodePlus = kHIDRemoteButtonCodeUp,
+ kHIDRemoteButtonCodePlusHold = kHIDRemoteButtonCodeUpHold,
+ kHIDRemoteButtonCodeMinus = kHIDRemoteButtonCodeDown,
+ kHIDRemoteButtonCodeMinusHold = kHIDRemoteButtonCodeDownHold,
+ kHIDRemoteButtonCodePlayPause = kHIDRemoteButtonCodeCenter,
+ kHIDRemoteButtonCodePlayPauseHold = kHIDRemoteButtonCodeCenterHold,
+
+ /* Special purpose codes */
+ kHIDRemoteButtonCodeIDChanged = (kHIDRemoteButtonCodeSpecialMask|(1L << 18L)), // (the ID of the connected remote has changed, you can safely ignore this)
+ #ifdef _HIDREMOTE_EXTENSIONS
+ #define _HIDREMOTE_EXTENSIONS_SECTION 1
+ #include "HIDRemoteAdditions.h"
+ #undef _HIDREMOTE_EXTENSIONS_SECTION
+ #endif /* _HIDREMOTE_EXTENSIONS */
+} HIDRemoteButtonCode;
+
+typedef enum
+{
+ kHIDRemoteModelUndetermined = 0L, // Assume a white plastic remote
+ kHIDRemoteModelWhitePlastic, // Signal *likely* to be coming from a white plastic remote
+ kHIDRemoteModelAluminum // Signal *definitely* coming from an aluminum remote
+} HIDRemoteModel;
+
+typedef enum
+{
+ kHIDRemoteAluminumRemoteSupportLevelNone = 0L, // This system has no support for the Aluminum Remote at all
+ kHIDRemoteAluminumRemoteSupportLevelEmulation, // This system possibly has support for the Aluminum Remote (via emulation)
+ kHIDRemoteAluminumRemoteSupportLevelNative // This system has native support for the Aluminum Remote
+} HIDRemoteAluminumRemoteSupportLevel;
+
+@class HIDRemote;
+
+#pragma mark -- Delegate protocol (mandatory) --
+@protocol HIDRemoteDelegate
+
+// Notification of button events
+- (void)hidRemote:(HIDRemote *)hidRemote // The instance of HIDRemote sending this
+ eventWithButton:(HIDRemoteButtonCode)buttonCode // Event for the button specified by code
+ isPressed:(BOOL)isPressed // The button was pressed (YES) / released (NO)
+ fromHardwareWithAttributes:(NSMutableDictionary *)attributes; // Information on the device this event comes from
+
+@optional
+
+// Notification of ID changes
+- (void)hidRemote:(HIDRemote *)hidRemote // Invoked when the user switched to a remote control with a different ID
+ remoteIDChangedOldID:(SInt32)old
+ newID:(SInt32)newID
+ forHardwareWithAttributes:(NSMutableDictionary *)attributes;
+
+// Notification about hardware additions/removals
+- (void)hidRemote:(HIDRemote *)hidRemote // Invoked when new hardware was found / added to HIDRemote's pool
+ foundNewHardwareWithAttributes:(NSMutableDictionary *)attributes;
+
+- (void)hidRemote:(HIDRemote *)hidRemote // Invoked when initialization of new hardware as requested failed
+ failedNewHardwareWithError:(NSError *)error;
+
+- (void)hidRemote:(HIDRemote *)hidRemote // Invoked when hardware was removed from HIDRemote's pool
+ releasedHardwareWithAttributes:(NSMutableDictionary *)attributes;
+
+// ### WARNING: Unless you know VERY PRECISELY what you are doing, do not implement any of the delegate methods below. ###
+
+// Matching of newly found receiver hardware
+- (BOOL)hidRemote:(HIDRemote *)hidRemote // Invoked when new hardware is inspected
+ inspectNewHardwareWithService:(io_service_t)service //
+ prematchResult:(BOOL)prematchResult; // Return YES if HIDRemote should go on with this hardware and try
+ // to use it, or NO if it should not be persued further.
+
+// Exlusive lock lending
+- (BOOL)hidRemote:(HIDRemote *)hidRemote
+ lendExclusiveLockToApplicationWithInfo:(NSDictionary *)applicationInfo;
+
+- (void)hidRemote:(HIDRemote *)hidRemote
+ exclusiveLockReleasedByApplicationWithInfo:(NSDictionary *)applicationInfo;
+
+- (BOOL)hidRemote:(HIDRemote *)hidRemote
+ shouldRetryExclusiveLockWithInfo:(NSDictionary *)applicationInfo;
+
+@end
+
+
+#pragma mark -- Actual header file for class --
+
+@interface HIDRemote : NSObject
+{
+ // IOMasterPort
+ mach_port_t _masterPort;
+
+ // Notification ports
+ IONotificationPortRef _notifyPort;
+ CFRunLoopSourceRef _notifyRLSource;
+
+ // Matching iterator
+ io_iterator_t _matchingServicesIterator;
+
+ // SecureInput notification
+ io_object_t _secureInputNotification;
+
+ // Service attributes
+ NSMutableDictionary *_serviceAttribMap;
+
+ // Mode
+ HIDRemoteMode _mode;
+ BOOL _autoRecover;
+ NSTimer *_autoRecoveryTimer;
+
+ // Delegate
+ NSObject <HIDRemoteDelegate> *_delegate;
+
+ // Last seen ID and remote model
+ SInt32 _lastSeenRemoteID;
+ HIDRemoteModel _lastSeenModel;
+ SInt32 _lastSeenModelRemoteID;
+
+ // Unused button codes
+ NSArray *_unusedButtonCodes;
+
+ // Simulate Plus/Minus Hold
+ BOOL _simulateHoldEvents;
+
+ // SecureEventInput workaround
+ BOOL _secureEventInputWorkAround;
+ UInt64 _lastSecureEventInputPIDSum;
+ uid_t _lastFrontUserSession;
+
+ // Exclusive lock lending
+ BOOL _exclusiveLockLending;
+ BOOL _sendExclusiveResourceReuseNotification;
+ NSNumber *_waitForReturnByPID;
+ NSNumber *_returnToPID;
+ BOOL _isRestarting;
+
+ // Status notifications
+ BOOL _sendStatusNotifications;
+ NSString *_pidString;
+
+ // Status
+ BOOL _applicationIsTerminating;
+ BOOL _isStopping;
+
+ // Thread safety
+ #ifdef HIDREMOTE_THREADSAFETY_HARDENED_NOTIFICATION_HANDLING /* #define HIDREMOTE_THREADSAFETY_HARDENED_NOTIFICATION_HANDLING if you're running your HIDRemote instance on a background thread (requires OS X 10.5 or later) */
+ NSThread *_runOnThread;
+ #endif
+}
+
+#pragma mark -- PUBLIC: Shared HID Remote --
++ (HIDRemote *)sharedHIDRemote;
+
+#pragma mark -- PUBLIC: System Information --
++ (BOOL)isCandelairInstalled;
++ (BOOL)isCandelairInstallationRequiredForRemoteMode:(HIDRemoteMode)remoteMode;
+- (HIDRemoteAluminumRemoteSupportLevel)aluminiumRemoteSystemSupportLevel;
+
+#pragma mark -- PUBLIC: Interface / API --
+- (BOOL)startRemoteControl:(HIDRemoteMode)hidRemoteMode;
+- (void)stopRemoteControl;
+
+- (BOOL)isStarted;
+- (HIDRemoteMode)startedInMode;
+
+- (unsigned)activeRemoteControlCount;
+
+- (SInt32)lastSeenRemoteControlID;
+
+- (void)setLastSeenModel:(HIDRemoteModel)aModel;
+- (HIDRemoteModel)lastSeenModel;
+
+- (void)setDelegate:(NSObject <HIDRemoteDelegate> *)newDelegate;
+- (NSObject <HIDRemoteDelegate> *)delegate;
+
+- (void)setSimulateHoldEvents:(BOOL)newSimulateHoldEvents;
+- (BOOL)simulateHoldEvents;
+
+- (void)setUnusedButtonCodes:(NSArray *)newArrayWithUnusedButtonCodesAsNSNumbers;
+- (NSArray *)unusedButtonCodes;
+
+#pragma mark -- PUBLIC: Expert APIs --
+- (void)setEnableSecureEventInputWorkaround:(BOOL)newEnableSecureEventInputWorkaround;
+- (BOOL)enableSecureEventInputWorkaround;
+
+- (void)setExclusiveLockLendingEnabled:(BOOL)newExclusiveLockLendingEnabled;
+- (BOOL)exclusiveLockLendingEnabled;
+
+- (BOOL)isApplicationTerminating;
+- (BOOL)isStopping;
+
+#pragma mark -- PRIVATE: HID Event handling --
+- (void)_handleButtonCode:(HIDRemoteButtonCode)buttonCode isPressed:(BOOL)isPressed hidAttribsDict:(NSMutableDictionary *)hidAttribsDict;
+- (void)_sendButtonCode:(HIDRemoteButtonCode)buttonCode isPressed:(BOOL)isPressed hidAttribsDict:(NSMutableDictionary *)hidAttribsDict;
+- (void)_hidEventFor:(io_service_t)hidDevice from:(IOHIDQueueInterface **)interface withResult:(IOReturn)result;
+
+#pragma mark -- PRIVATE: Service setup and destruction --
+- (BOOL)_prematchService:(io_object_t)service;
+- (HIDRemoteButtonCode)buttonCodeForUsage:(unsigned int)usage usagePage:(unsigned int)usagePage;
+- (BOOL)_setupService:(io_object_t)service;
+- (void)_destructService:(io_object_t)service;
+
+#pragma mark -- PRIVATE: Distributed notifiations handling --
+- (void)_postStatusWithAction:(NSString *)action;
+- (void)_handleNotifications:(NSNotification *)notification;
+- (void)_setSendStatusNotifications:(BOOL)doSend;
+- (BOOL)_sendStatusNotifications;
+
+#pragma mark -- PRIVATE: Application becomes active / inactive handling for kHIDRemoteModeExclusiveAuto --
+- (void)_appStatusChanged:(NSNotification *)notification;
+- (void)_delayedAutoRecovery:(NSTimer *)aTimer;
+
+#pragma mark -- PRIVATE: Notification handling --
+- (void)_serviceMatching:(io_iterator_t)iterator;
+- (void)_serviceNotificationFor:(io_service_t)service messageType:(natural_t)messageType messageArgument:(void *)messageArgument;
+- (void)_updateSessionInformation;
+- (void)_secureInputNotificationFor:(io_service_t)service messageType:(natural_t)messageType messageArgument:(void *)messageArgument;
+
+@end
+
+#pragma mark -- Information attribute keys --
+extern NSString *kHIDRemoteManufacturer;
+extern NSString *kHIDRemoteProduct;
+extern NSString *kHIDRemoteTransport;
+
+#pragma mark -- Internal/Expert attribute keys (AKA: don't touch these unless you really, really, REALLY know what you do) --
+extern NSString *kHIDRemoteCFPluginInterface;
+extern NSString *kHIDRemoteHIDDeviceInterface;
+extern NSString *kHIDRemoteCookieButtonCodeLUT;
+extern NSString *kHIDRemoteHIDQueueInterface;
+extern NSString *kHIDRemoteServiceNotification;
+extern NSString *kHIDRemoteCFRunLoopSource;
+extern NSString *kHIDRemoteLastButtonPressed;
+extern NSString *kHIDRemoteService;
+extern NSString *kHIDRemoteSimulateHoldEventsTimer;
+extern NSString *kHIDRemoteSimulateHoldEventsOriginButtonCode;
+extern NSString *kHIDRemoteAluminumRemoteSupportLevel;
+extern NSString *kHIDRemoteAluminumRemoteSupportOnDemand;
+
+#pragma mark -- Distributed notifications --
+extern NSString *kHIDRemoteDNHIDRemotePing;
+extern NSString *kHIDRemoteDNHIDRemoteRetry;
+extern NSString *kHIDRemoteDNHIDRemoteStatus;
+
+extern NSString *kHIDRemoteDNHIDRemoteRetryGlobalObject;
+
+#pragma mark -- Distributed notifications userInfo keys and values --
+extern NSString *kHIDRemoteDNStatusHIDRemoteVersionKey;
+extern NSString *kHIDRemoteDNStatusPIDKey;
+extern NSString *kHIDRemoteDNStatusModeKey;
+extern NSString *kHIDRemoteDNStatusUnusedButtonCodesKey;
+extern NSString *kHIDRemoteDNStatusRemoteControlCountKey;
+extern NSString *kHIDRemoteDNStatusReturnToPIDKey;
+extern NSString *kHIDRemoteDNStatusActionKey;
+extern NSString *kHIDRemoteDNStatusActionStart;
+extern NSString *kHIDRemoteDNStatusActionStop;
+extern NSString *kHIDRemoteDNStatusActionUpdate;
+extern NSString *kHIDRemoteDNStatusActionNoNeed;
+
+#pragma mark -- Driver compatibility flags --
+typedef enum
+{
+ kHIDRemoteCompatibilityFlagsStandardHIDRemoteDevice = 1L,
+} HIDRemoteCompatibilityFlags;
diff --git a/osdep/ar/HIDRemote.m b/osdep/ar/HIDRemote.m
new file mode 100644
index 0000000000..f05628c040
--- /dev/null
+++ b/osdep/ar/HIDRemote.m
@@ -0,0 +1,2068 @@
+//
+// HIDRemote.m
+// HIDRemote V1.2 (27th May 2011)
+//
+// Created by Felix Schwarz on 06.04.07.
+// Copyright 2007-2011 IOSPIRIT GmbH. All rights reserved.
+//
+// The latest version of this class is available at
+// http://www.iospirit.com/developers/hidremote/
+//
+// ** LICENSE *************************************************************************
+//
+// Copyright (c) 2007-2011 IOSPIRIT GmbH (http://www.iospirit.com/)
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without modification,
+// are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice, this list
+// of conditions and the following disclaimer.
+//
+// * Redistributions in binary form must reproduce the above copyright notice, this
+// list of conditions and the following disclaimer in the documentation and/or other
+// materials provided with the distribution.
+//
+// * Neither the name of IOSPIRIT GmbH nor the names of its contributors may be used to
+// endorse or promote products derived from this software without specific prior
+// written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
+// SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+// TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+// BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+// DAMAGE.
+//
+// ************************************************************************************
+
+// ************************************************************************************
+// ********************************** DOCUMENTATION ***********************************
+// ************************************************************************************
+//
+// - a reference is available at http://www.iospirit.com/developers/hidremote/reference/
+// - for a guide, please see http://www.iospirit.com/developers/hidremote/guide/
+//
+// ************************************************************************************
+
+#import "HIDRemote.h"
+
+// Callback Prototypes
+static void HIDEventCallback( void * target,
+ IOReturn result,
+ void * refcon,
+ void * sender);
+
+static void ServiceMatchingCallback( void *refCon,
+ io_iterator_t iterator);
+
+static void ServiceNotificationCallback(void * refCon,
+ io_service_t service,
+ natural_t messageType,
+ void * messageArgument);
+
+static void SecureInputNotificationCallback( void * refCon,
+ io_service_t service,
+ natural_t messageType,
+ void * messageArgument);
+
+// Shared HIDRemote instance
+static HIDRemote *sHIDRemote = nil;
+
+@implementation HIDRemote
+
+#pragma mark -- Init, dealloc & shared instance --
+
++ (HIDRemote *)sharedHIDRemote
+{
+ if (sHIDRemote==nil)
+ {
+ sHIDRemote = [[HIDRemote alloc] init];
+ }
+
+ return (sHIDRemote);
+}
+
+- (id)init
+{
+ if ((self = [super init]) != nil)
+ {
+ #ifdef HIDREMOTE_THREADSAFETY_HARDENED_NOTIFICATION_HANDLING
+ _runOnThread = [[NSThread currentThread] retain];
+ #endif
+
+ // Detect application becoming active/inactive
+ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_appStatusChanged:) name:NSApplicationDidBecomeActiveNotification object:NSApp];
+ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_appStatusChanged:) name:NSApplicationWillResignActiveNotification object:NSApp];
+ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_appStatusChanged:) name:NSApplicationWillTerminateNotification object:NSApp];
+
+ // Handle distributed notifications
+ _pidString = [[NSString alloc] initWithFormat:@"%d", getpid()];
+
+ [[NSDistributedNotificationCenter defaultCenter] addObserver:self selector:@selector(_handleNotifications:) name:kHIDRemoteDNHIDRemotePing object:nil];
+ [[NSDistributedNotificationCenter defaultCenter] addObserver:self selector:@selector(_handleNotifications:) name:kHIDRemoteDNHIDRemoteRetry object:kHIDRemoteDNHIDRemoteRetryGlobalObject];
+ [[NSDistributedNotificationCenter defaultCenter] addObserver:self selector:@selector(_handleNotifications:) name:kHIDRemoteDNHIDRemoteRetry object:_pidString];
+
+ // Enabled by default: simulate hold events for plus/minus
+ _simulateHoldEvents = YES;
+
+ // Enabled by default: work around for a locking issue introduced with Security Update 2008-004 / 10.4.9 and beyond (credit for finding this workaround goes to Martin Kahr)
+ _secureEventInputWorkAround = YES;
+ _secureInputNotification = 0;
+
+ // Initialize instance variables
+ _lastSeenRemoteID = -1;
+ _lastSeenModel = kHIDRemoteModelUndetermined;
+ _unusedButtonCodes = [[NSMutableArray alloc] init];
+ _exclusiveLockLending = NO;
+ _sendExclusiveResourceReuseNotification = YES;
+ _applicationIsTerminating = NO;
+
+ // Send status notifications
+ _sendStatusNotifications = YES;
+ }
+
+ return (self);
+}
+
+- (void)dealloc
+{
+ [[NSNotificationCenter defaultCenter] removeObserver:self name:NSApplicationWillTerminateNotification object:NSApp];
+ [[NSNotificationCenter defaultCenter] removeObserver:self name:NSApplicationWillResignActiveNotification object:NSApp];
+ [[NSNotificationCenter defaultCenter] removeObserver:self name:NSApplicationDidBecomeActiveNotification object:NSApp];
+
+ [[NSDistributedNotificationCenter defaultCenter] removeObserver:self name:kHIDRemoteDNHIDRemotePing object:nil];
+ [[NSDistributedNotificationCenter defaultCenter] removeObserver:self name:kHIDRemoteDNHIDRemoteRetry object:kHIDRemoteDNHIDRemoteRetryGlobalObject];
+ [[NSDistributedNotificationCenter defaultCenter] removeObserver:self name:kHIDRemoteDNHIDRemoteRetry object:_pidString];
+ [[NSDistributedNotificationCenter defaultCenter] removeObserver:self name:nil object:nil]; /* As demanded by the documentation for -[NSDistributedNotificationCenter removeObserver:name:object:] */
+
+ [self stopRemoteControl];
+
+ [self setExclusiveLockLendingEnabled:NO];
+
+ [self setDelegate:nil];
+
+ if (_unusedButtonCodes != nil)
+ {
+ [_unusedButtonCodes release];
+ _unusedButtonCodes = nil;
+ }
+
+ #ifdef HIDREMOTE_THREADSAFETY_HARDENED_NOTIFICATION_HANDLING
+ [_runOnThread release];
+ _runOnThread = nil;
+ #endif
+
+ [_pidString release];
+ _pidString = nil;
+
+ [super dealloc];
+}
+
+#pragma mark -- PUBLIC: System Information --
++ (BOOL)isCandelairInstalled
+{
+ mach_port_t masterPort = 0;
+ kern_return_t kernResult;
+ io_service_t matchingService = 0;
+ BOOL isInstalled = NO;
+
+ kernResult = IOMasterPort(MACH_PORT_NULL, &masterPort);
+ if ((kernResult!=kIOReturnSuccess) || (masterPort==0)) { return(NO); }
+
+ if ((matchingService = IOServiceGetMatchingService(masterPort, IOServiceMatching("IOSPIRITIRController"))) != 0)
+ {
+ isInstalled = YES;
+ IOObjectRelease((io_object_t) matchingService);
+ }
+
+ mach_port_deallocate(mach_task_self(), masterPort);
+
+ return (isInstalled);
+}
+
++ (BOOL)isCandelairInstallationRequiredForRemoteMode:(HIDRemoteMode)remoteMode
+{
+ SInt32 systemVersion = 0;
+
+ // Determine OS version
+ if (Gestalt(gestaltSystemVersion, &systemVersion) == noErr)
+ {
+ switch (systemVersion)
+ {
+ case 0x1060: // OS 10.6
+ case 0x1061: // OS 10.6.1
+ // OS X 10.6(.0) and OS X 10.6.1 require the Candelair driver for to be installed,
+ // so that third party apps can acquire an exclusive lock on the receiver HID Device
+ // via IOKit.
+
+ switch (remoteMode)
+ {
+ case kHIDRemoteModeExclusive:
+ case kHIDRemoteModeExclusiveAuto:
+ if (![self isCandelairInstalled])
+ {
+ return (YES);
+ }
+ break;
+ }
+ break;
+ }
+ }
+
+ return (NO);
+}
+
+- (HIDRemoteAluminumRemoteSupportLevel)aluminiumRemoteSystemSupportLevel
+{
+ HIDRemoteAluminumRemoteSupportLevel supportLevel = kHIDRemoteAluminumRemoteSupportLevelNone;
+ NSEnumerator *attribDictsEnum;
+ NSDictionary *hidAttribsDict;
+
+ attribDictsEnum = [_serviceAttribMap objectEnumerator];
+
+ while ((hidAttribsDict = [attribDictsEnum nextObject]) != nil)
+ {
+ NSNumber *deviceSupportLevel;
+
+ if ((deviceSupportLevel = [hidAttribsDict objectForKey:kHIDRemoteAluminumRemoteSupportLevel]) != nil)
+ {
+ if ([deviceSupportLevel intValue] > (int)supportLevel)
+ {
+ supportLevel = [deviceSupportLevel intValue];
+ }
+ }
+ }
+
+ return (supportLevel);
+}
+
+#pragma mark -- PUBLIC: Interface / API --
+- (BOOL)startRemoteControl:(HIDRemoteMode)hidRemoteMode
+{
+ if ((_mode == kHIDRemoteModeNone) && (hidRemoteMode != kHIDRemoteModeNone))
+ {
+ kern_return_t kernReturn;
+ CFMutableDictionaryRef matchDict=NULL;
+ io_service_t rootService;
+
+ do
+ {
+ // Get IOKit master port
+ kernReturn = IOMasterPort(bootstrap_port, &_masterPort);
+ if ((kernReturn!=kIOReturnSuccess) || (_masterPort==0)) { break; }
+
+ // Setup notification port
+ _notifyPort = IONotificationPortCreate(_masterPort);
+
+ if ((_notifyRLSource = IONotificationPortGetRunLoopSource(_notifyPort)) != NULL)
+ {
+ CFRunLoopAddSource( CFRunLoopGetCurrent(),
+ _notifyRLSource,
+ kCFRunLoopCommonModes);
+ }
+ else
+ {
+ break;
+ }
+
+ // Setup SecureInput notification
+ if ((hidRemoteMode == kHIDRemoteModeExclusive) || (hidRemoteMode == kHIDRemoteModeExclusiveAuto))
+ {
+ if ((rootService = IORegistryEntryFromPath(_masterPort, kIOServicePlane ":/")) != 0)
+ {
+ kernReturn = IOServiceAddInterestNotification( _notifyPort,
+ rootService,
+ kIOBusyInterest,
+ SecureInputNotificationCallback,
+ (void *)self,
+ &_secureInputNotification);
+ if (kernReturn != kIOReturnSuccess) { break; }
+
+ [self _updateSessionInformation];
+ }
+ else
+ {
+ break;
+ }
+ }
+
+ // Setup notification matching dict
+ matchDict = IOServiceMatching(kIOHIDDeviceKey);
+ CFRetain(matchDict);
+
+ // Actually add notification
+ kernReturn = IOServiceAddMatchingNotification( _notifyPort,
+ kIOFirstMatchNotification,
+ matchDict, // one reference count consumed by this call
+ ServiceMatchingCallback,
+ (void *) self,
+ &_matchingServicesIterator);
+ if (kernReturn != kIOReturnSuccess) { break; }
+
+ // Setup serviceAttribMap
+ _serviceAttribMap = [[NSMutableDictionary alloc] init];
+ if (_serviceAttribMap==nil) { break; }
+
+ // Phew .. everything went well!
+ _mode = hidRemoteMode;
+ CFRelease(matchDict);
+
+ [self _serviceMatching:_matchingServicesIterator];
+
+ [self _postStatusWithAction:kHIDRemoteDNStatusActionStart];
+
+ return (YES);
+
+ }while(0);
+
+ // An error occured. Do necessary clean up.
+ if (matchDict!=NULL)
+ {
+ CFRelease(matchDict);
+ matchDict = NULL;
+ }
+
+ [self stopRemoteControl];
+ }
+
+ return (NO);
+}
+
+- (void)stopRemoteControl
+{
+ UInt32 serviceCount = 0;
+
+ _autoRecover = NO;
+ _isStopping = YES;
+
+ if (_autoRecoveryTimer!=nil)
+ {
+ [_autoRecoveryTimer invalidate];
+ [_autoRecoveryTimer release];
+ _autoRecoveryTimer = nil;
+ }
+
+ if (_serviceAttribMap!=nil)
+ {
+ NSDictionary *cloneDict = [[NSDictionary alloc] initWithDictionary:_serviceAttribMap];
+
+ if (cloneDict!=nil)
+ {
+ NSEnumerator *mapKeyEnum = [cloneDict keyEnumerator];
+ NSNumber *serviceValue;
+
+ while ((serviceValue = [mapKeyEnum nextObject]) != nil)
+ {
+ [self _destructService:(io_object_t)[serviceValue unsignedIntValue]];
+ serviceCount++;
+ };
+
+ [cloneDict release];
+ cloneDict = nil;
+ }
+
+ [_serviceAttribMap release];
+ _serviceAttribMap = nil;
+ }
+
+ if (_matchingServicesIterator!=0)
+ {
+ IOObjectRelease((io_object_t) _matchingServicesIterator);
+ _matchingServicesIterator = 0;
+ }
+
+ if (_secureInputNotification!=0)
+ {
+ IOObjectRelease((io_object_t) _secureInputNotification);
+ _secureInputNotification = 0;
+ }
+
+ if (_notifyRLSource!=NULL)
+ {
+ CFRunLoopSourceInvalidate(_notifyRLSource);
+ _notifyRLSource = NULL;
+ }
+
+ if (_notifyPort!=NULL)
+ {
+ IONotificationPortDestroy(_notifyPort);
+ _notifyPort = NULL;
+ }
+
+ if (_masterPort!=0)
+ {
+ mach_port_deallocate(mach_task_self(), _masterPort);
+ _masterPort = 0;
+ }
+
+ if (_returnToPID!=nil)
+ {
+ [_returnToPID release];
+ _returnToPID = nil;
+ }
+
+ if (_mode!=kHIDRemoteModeNone)
+ {
+ // Post status
+ [self _postStatusWithAction:kHIDRemoteDNStatusActionStop];
+
+ if (_sendStatusNotifications)
+ {
+ // In case we were not ready to lend it earlier, tell other HIDRemote apps that the resources (if any were used) are now again available for use by other applications
+ if (((_mode==kHIDRemoteModeExclusive) || (_mode==kHIDRemoteModeExclusiveAuto)) && (_sendExclusiveResourceReuseNotification==YES) && (_exclusiveLockLending==NO) && (serviceCount>0))
+ {
+ _mode = kHIDRemoteModeNone;
+
+ if (!_isRestarting)
+ {
+ [[NSDistributedNotificationCenter defaultCenter] postNotificationName:kHIDRemoteDNHIDRemoteRetry
+ object:kHIDRemoteDNHIDRemoteRetryGlobalObject
+ userInfo:[NSDictionary dictionaryWithObjectsAndKeys:
+ [NSNumber numberWithUnsignedInt:(unsigned int)getpid()], kHIDRemoteDNStatusPIDKey,
+ [[NSBundle mainBundle] bundleIdentifier], (NSString *)kCFBundleIdentifierKey,
+ nil]
+ deliverImmediately:YES];
+ }
+ }
+ }
+ }
+
+ _mode = kHIDRemoteModeNone;
+ _isStopping = NO;
+}
+
+- (BOOL)isStarted
+{
+ return (_mode != kHIDRemoteModeNone);
+}
+
+- (HIDRemoteMode)startedInMode
+{
+ return (_mode);
+}
+
+- (unsigned)activeRemoteControlCount
+{
+ return ([_serviceAttribMap count]);
+}
+
+- (SInt32)lastSeenRemoteControlID
+{
+ return (_lastSeenRemoteID);
+}
+
+- (HIDRemoteModel)lastSeenModel
+{
+ return (_lastSeenModel);
+}
+
+- (void)setLastSeenModel:(HIDRemoteModel)aModel
+{
+ _lastSeenModel = aModel;
+}
+
+- (void)setSimulateHoldEvents:(BOOL)newSimulateHoldEvents
+{
+ _simulateHoldEvents = newSimulateHoldEvents;
+}
+
+- (BOOL)simulateHoldEvents
+{
+ return (_simulateHoldEvents);
+}
+
+- (NSArray *)unusedButtonCodes
+{
+ return (_unusedButtonCodes);
+}
+
+- (void)setUnusedButtonCodes:(NSArray *)newArrayWithUnusedButtonCodesAsNSNumbers
+{
+ [newArrayWithUnusedButtonCodesAsNSNumbers retain];
+ [_unusedButtonCodes release];
+
+ _unusedButtonCodes = newArrayWithUnusedButtonCodesAsNSNumbers;
+
+ [self _postStatusWithAction:kHIDRemoteDNStatusActionUpdate];
+}
+
+- (void)setDelegate:(NSObject <HIDRemoteDelegate> *)newDelegate
+{
+ _delegate = newDelegate;
+}
+
+- (NSObject <HIDRemoteDelegate> *)delegate
+{
+ return (_delegate);
+}
+
+#pragma mark -- PUBLIC: Expert APIs --
+- (void)setEnableSecureEventInputWorkaround:(BOOL)newEnableSecureEventInputWorkaround
+{
+ _secureEventInputWorkAround = newEnableSecureEventInputWorkaround;
+}
+
+- (BOOL)enableSecureEventInputWorkaround
+{
+ return (_secureEventInputWorkAround);
+}
+
+- (void)setExclusiveLockLendingEnabled:(BOOL)newExclusiveLockLendingEnabled
+{
+ if (newExclusiveLockLendingEnabled != _exclusiveLockLending)
+ {
+ _exclusiveLockLending = newExclusiveLockLendingEnabled;
+
+ if (_exclusiveLockLending)
+ {
+ [[NSDistributedNotificationCenter defaultCenter] addObserver:self selector:@selector(_handleNotifications:) name:kHIDRemoteDNHIDRemoteStatus object:nil];
+ }
+ else
+ {
+ [[NSDistributedNotificationCenter defaultCenter] removeObserver:self name:kHIDRemoteDNHIDRemoteStatus object:nil];
+
+ [_waitForReturnByPID release];
+ _waitForReturnByPID = nil;
+ }
+ }
+}
+
+- (BOOL)exclusiveLockLendingEnabled
+{
+ return (_exclusiveLockLending);
+}
+
+- (void)setSendExclusiveResourceReuseNotification:(BOOL)newSendExclusiveResourceReuseNotification
+{
+ _sendExclusiveResourceReuseNotification = newSendExclusiveResourceReuseNotification;
+}
+
+- (BOOL)sendExclusiveResourceReuseNotification
+{
+ return (_sendExclusiveResourceReuseNotification);
+}
+
+- (BOOL)isApplicationTerminating
+{
+ return (_applicationIsTerminating);
+}
+
+- (BOOL)isStopping
+{
+ return (_isStopping);
+}
+
+#pragma mark -- PRIVATE: Application becomes active / inactive handling for kHIDRemoteModeExclusiveAuto --
+- (void)_appStatusChanged:(NSNotification *)notification
+{
+ #ifdef HIDREMOTE_THREADSAFETY_HARDENED_NOTIFICATION_HANDLING
+ if ([self respondsToSelector:@selector(performSelector:onThread:withObject:waitUntilDone:)]) // OS X 10.5+ only
+ {
+ if ([NSThread currentThread] != _runOnThread)
+ {
+ if ([[notification name] isEqual:NSApplicationDidBecomeActiveNotification])
+ {
+ if (!_autoRecover)
+ {
+ return;
+ }
+ }
+
+ if ([[notification name] isEqual:NSApplicationWillResignActiveNotification])
+ {
+ if (_mode != kHIDRemoteModeExclusiveAuto)
+ {
+ return;
+ }
+ }
+
+ [self performSelector:@selector(_appStatusChanged:) onThread:_runOnThread withObject:notification waitUntilDone:[[notification name] isEqual:NSApplicationWillTerminateNotification]];
+ return;
+ }
+ }
+ #endif
+
+ if (notification!=nil)
+ {
+ if (_autoRecoveryTimer!=nil)
+ {
+ [_autoRecoveryTimer invalidate];
+ [_autoRecoveryTimer release];
+ _autoRecoveryTimer = nil;
+ }
+
+ if ([[notification name] isEqual:NSApplicationDidBecomeActiveNotification])
+ {
+ if (_autoRecover)
+ {
+ // Delay autorecover by 0.1 to avoid race conditions
+ if ((_autoRecoveryTimer = [[NSTimer alloc] initWithFireDate:[NSDate dateWithTimeIntervalSinceNow:0.1] interval:0.1 target:self selector:@selector(_delayedAutoRecovery:) userInfo:nil repeats:NO]) != nil)
+ {
+ // Using CFRunLoopAddTimer instead of [[NSRunLoop currentRunLoop] addTimer:.. for consistency with run loop modes.
+ // The kCFRunLoopCommonModes counterpart NSRunLoopCommonModes is only available in 10.5 and later, whereas this code
+ // is designed to be also compatible with 10.4. CFRunLoopTimerRef is "toll-free-bridged" with NSTimer since 10.0.
+ CFRunLoopAddTimer(CFRunLoopGetCurrent(), (CFRunLoopTimerRef)_autoRecoveryTimer, kCFRunLoopCommonModes);
+ }
+ }
+ }
+
+ if ([[notification name] isEqual:NSApplicationWillResignActiveNotification])
+ {
+ if (_mode == kHIDRemoteModeExclusiveAuto)
+ {
+ [self stopRemoteControl];
+ _autoRecover = YES;
+ }
+ }
+
+ if ([[notification name] isEqual:NSApplicationWillTerminateNotification])
+ {
+ _applicationIsTerminating = YES;
+
+ if ([self isStarted])
+ {
+ [self stopRemoteControl];
+ }
+ }
+ }
+}
+
+- (void)_delayed