diff options
Diffstat (limited to 'osdep')
-rw-r--r-- | osdep/ar/HIDRemote.h | 378 | ||||
-rw-r--r-- | osdep/ar/HIDRemote.m | 2068 | ||||
-rw-r--r-- | osdep/macosx_application_objc.h | 6 | ||||
-rw-r--r-- | osdep/macosx_events.h | 2 | ||||
-rw-r--r-- | osdep/macosx_events.m | 61 |
5 files changed, 2512 insertions, 3 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; |