diff options
author | maestrodd <maestrodd@svn> | 2009-10-23 14:54:08 +0000 |
---|---|---|
committer | maestrodd <maestrodd@svn> | 2009-10-23 14:54:08 +0000 |
commit | e29c9df6d5228423fcdbad090ef64277e2eb146d (patch) | |
tree | 2a981446ea1e71bf32cad736a028528d82469115 /tools/EventClients | |
parent | 0ddd418f491ca9cb3338e583fadd67a5a4bc1cde (diff) |
[OSX] changed to HIDRemote backend for XBMCHelper (thanks to IOSpirit for openingn it up) + few cosmetics
git-svn-id: https://xbmc.svn.sourceforge.net/svnroot/xbmc/trunk@23927 568bbfeb-2a22-0410-94d2-cc84cf5bfa90
Diffstat (limited to 'tools/EventClients')
22 files changed, 2293 insertions, 2119 deletions
diff --git a/tools/EventClients/Clients/OSXRemote/HIDRemote.h b/tools/EventClients/Clients/OSXRemote/HIDRemote.h new file mode 100644 index 0000000000..d2a7cf8796 --- /dev/null +++ b/tools/EventClients/Clients/OSXRemote/HIDRemote.h @@ -0,0 +1,484 @@ +// +// HIDRemote.h +// HIDRemote V1.0 +// +// Created by Felix Schwarz on 06.04.07. +// Copyright 2007-2009 IOSPIRIT GmbH. All rights reserved. +// +// ** LICENSE ************************************************************************* +// +// Copyright (c) 2007-2009 IOSPIRIT GmbH +// 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. +// +// ************************************************************************************ + +#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, + + /* HID Remote standard codes - you'll be able to receive all of these in your HIDRemote delegate */ + kHIDRemoteButtonCodePlus, + kHIDRemoteButtonCodeMinus, + kHIDRemoteButtonCodeLeft, + kHIDRemoteButtonCodeRight, + kHIDRemoteButtonCodePlayPause, + kHIDRemoteButtonCodeMenu, + + /* Masks */ + kHIDRemoteButtonCodeCodeMask = 0xFFL, + kHIDRemoteButtonCodeHoldMask = (1L << 16L), + kHIDRemoteButtonCodeSpecialMask = (1L << 17L), + + /* Hold button codes */ + kHIDRemoteButtonCodePlusHold = (kHIDRemoteButtonCodeHoldMask|kHIDRemoteButtonCodePlus), + kHIDRemoteButtonCodeMinusHold = (kHIDRemoteButtonCodeHoldMask|kHIDRemoteButtonCodeMinus), + kHIDRemoteButtonCodeLeftHold = (kHIDRemoteButtonCodeHoldMask|kHIDRemoteButtonCodeLeft), + kHIDRemoteButtonCodeRightHold = (kHIDRemoteButtonCodeHoldMask|kHIDRemoteButtonCodeRight), + kHIDRemoteButtonCodePlayPauseHold = (kHIDRemoteButtonCodeHoldMask|kHIDRemoteButtonCodePlayPause), + kHIDRemoteButtonCodeMenuHold = (kHIDRemoteButtonCodeHoldMask|kHIDRemoteButtonCodeMenu), + + /* 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; + +@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) + +@optional + +// Notification of ID changes +- (void)hidRemote:(HIDRemote *)hidRemote + remoteIDChangedOldID:(SInt32)old // Invoked when the user switched to a remote control with a different ID + newID:(SInt32)newID; + +// 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 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; + + // Service attributes + NSMutableDictionary *_serviceAttribMap; + + // Mode + HIDRemoteMode _mode; + BOOL _autoRecover; + NSTimer *_autoRecoveryTimer; + + // Delegate + NSObject <HIDRemoteDelegate> *_delegate; + + // Last seen ID + SInt32 _lastSeenRemoteID; + + // Unused button codes + NSArray *_unusedButtonCodes; + + // Simulate Plus/Minus Hold + BOOL _simulateHoldEvents; + + // SecureEventInput workaround + BOOL _secureEventInputWorkAround; + BOOL _statusSecureEventInputWorkAroundEnabled; + + // Exclusive lock lending + BOOL _exclusiveLockLending; + NSNumber *_waitForReturnByPID; + NSNumber *_returnToPID; +} + +#pragma mark -- PUBLIC: Shared HID Remote -- +/* + DESCRIPTION + It is possible to alloc & init multiple instances of this class. However, this is not recommended unless + you subclass it and build something different. Instead of allocating & initializing the instance yourself, + you can make use of the +sharedHIDRemote singleton. + + RESULT + The HIDRemote instance globally shared in your application. You should not -release the returned object. +*/ ++ (HIDRemote *)sharedHIDRemote; + +#pragma mark -- PUBLIC: System Information -- +/* + DESCRIPTION + Determine whether the Candelair driver version 1.7.0 or later is installed. + + RESULT + YES, if it is installed. NO, if it isn't. +*/ ++ (BOOL)isCandelairInstalled; + +/* + DESCRIPTION + Determine whether the user needs to install the Candelair driver in order for your application to get + access to the IR Receiver in a specific mode. + + RESULT + YES, if the user runs your application on an operating system version that makes the installation of + the Candelair driver necessary for your application to get access to the IR Receiver in the specified + mode. + NO, if the operating system version in use either doesn't make the installation of the Candelair driver + a necessity - or - if it is already installed. + + SAMPLE CODE + Please see DemoController.m from the HIDRemoteSample project for a reusable example on how to make best + use of this method in your code. +*/ ++ (BOOL)isCandelairInstallationRequiredForRemoteMode:(HIDRemoteMode)remoteMode; + +#pragma mark -- PUBLIC: Interface / API -- +/* + DESCRIPTION + Starts the HIDRemote in the respective mode kHIDRemoteModeShared, kHIDRemoteModeExclusive or + kHIDRemoteModeExclusiveAuto. + + RESULT + YES, if setup was successful. NO, if an error occured during setup. Note that a successful setup + does not mean that you gained the respective level of access or that remote control hardware was + actually found. This is only the case if -activeRemoteControlCount returns a value + greater zero. I.e. your setup code could look like this: + + if ((hidRemoteControl = [HIDRemoteControl sharedHIDRemote]) != nil) + { + [hidRemoteControl setDelegate:myDelegate]; + + if ([HIDRemote isCandelairInstallationRequiredForRemoteMode:kHIDRemoteModeExclusiveAuto]) + { + NSLog(@"Installation of Candelair required."); // See DemoController.m for a reusable code snippet presenting an + // alert and offering to open related URLs + } + else + { + if ([hidRemoteControl startRemoteControl:kHIDRemoteModeExclusiveAuto]) + { + NSLog(@"Driver has started successfully."); + + if ([hidRemoteControl activeRemoteControlCount]) + { + NSLog(@"Driver has found %d remotes.", [hidRemoteControl activeRemoteControlCount]); + } + else + { + NSLog(@"Driver has not found any remotes it could use. Will use remotes as they become available."); + } + } + else + { + // .. Setup failed .. + } + } + } +*/ +- (BOOL)startRemoteControl:(HIDRemoteMode)hidRemoteMode; + +/* + DESCRIPTION + Stops the HIDRemote. You will no longer get remote control events after this. Other applications can + then access the remote again. To get a lock on the HIDRemote again, make use of -startRemoteControl:. +*/ +- (void)stopRemoteControl; + +/* + DESCRIPTION + Determine, whether the HIDRemote has been started with -startRemoteControl:. + + RESULT + YES, if it was started. NO, if it was not. +*/ +- (BOOL)isStarted; + +/* + DESCRIPTION + Determine the number of remote controls HIDRemote has currently opened. This is usually 1 on current systems. +*/ +- (unsigned)activeRemoteControlCount; + +/* + DESCRIPTION + Returns the ID of the remote from which the button press was received from last. You can sign up your delegate for + ID change notifications by implementing the (optional) -hidRemote:remoteIDChangedOldID:newID: selector. + + RESULT + Returns the ID of the last seen remote. Returns -1, if the ID is unknown. +*/ +- (SInt32)lastSeenRemoteControlID; + +/* + DESCRIPTION + Set a new delegate object. This object has to implement the HIDRemoteDelegate protocol. If it is also implementing + the optional HIDRemoteDelegate protocol methods, it will be able to receive additional notifications and events. + + IMPORTANT + The delegate is not retained. Make sure you execute a -[hidRemoteInstance setDelegate:nil] in the dealloc method of + your delegate. +*/ +- (void)setDelegate:(NSObject <HIDRemoteDelegate> *)newDelegate; + +/* + DESCRIPTION + Get the currently set delegate object. + + RESULT + The currently set delegate object. +*/ +- (NSObject <HIDRemoteDelegate> *)delegate; + +/* + DESCRIPTION + Set whether hold events should be simulated for the + and - buttons. The simulation is active by default. This value + should only be changed when no button is currently pressed (f.ex. before calling -startRemoteControl:). The behaviour + is undefined if a button press is currently in progress. +*/ +- (void)setSimulateHoldEvents:(BOOL)newSimulateHoldEvents; + +/* + DESCRIPTION + Determine whether the simulation of hold events for the + and - buttons is currently active. + + RESULT + YES or NO depending on whether the simulation is currently active. +*/ +- (BOOL)simulateHoldEvents; + +/* + DESCRIPTION + Set an array of NSNumbers with HIDRemoteButtonCodes that are not used by your application. This is empty by default. + By providing this information, you improve interoperation with popular remote control solutions such as Remote Buddy. + If, for example, you don't use the MenuHold button code, you'd express it like this in your sourcecode: + + if (hidRemote = [HIDRemote sharedHIDRemote]) + { + // .. + + [hidRemote setUnusedButtonCodes:[NSArray arrayWithObjects:[NSNumber numberWithInt:(int)kHIDRemoteButtonCodeMenuHold], nil]]; + + // .. + } + + Advanced remote control solutions such as Remote Buddy do then know that you're not using the MenuHold button code and + can automatically create a mapping table for your application, with all buttons presses except MenuHold being forwarded + to your application. For MenuHold, Remote Buddy might map an action to open its own menu. +*/ +- (void)setUnusedButtonCodes:(NSArray *)newArrayWithUnusedButtonCodesAsNSNumbers; + +/* + DESCRIPTION + Return an array of NSNumbers with HIDRemoteButtonCodes your application does not use. For more information, see the + description for -setUnusedButtonCodes: + + RESULT + An array of NSNumbers with HIDRemoteButtonCodes your application does not use. +*/ +- (NSArray *)unusedButtonCodes; + +#pragma mark -- PUBLIC: Expert APIs -- + +/* + DESCRIPTION + Enables/disables a workaround to a locking issue introduced with Security Update 2008-004 / 10.4.9 and beyond. Essentially, + without this workaround enabled, using an application that uses a password textfield would degrade exclusive locks to shared + locks with the result being that both normal OS X as well as your application would react to the same HID event when really + only your application should. Credit for finding this workaround goes to Martin Kahr. + + Enabled by default. +*/ +- (void)setEnableSecureEventInputWorkaround:(BOOL)newEnableSecureEventInputWorkaround; + +/* + DESCRIPTION + Determine whether aforementioned workaround is active. + + RESULT + YES or NO. +*/ +- (BOOL)enableSecureEventInputWorkaround; + +/* + DESCRIPTION + Enables/disables lending of the exclusive lock to other applications when in kHIDRemoteModeExclusive mode. + + Enable this option only when you are writing a background application that keeps a permanent, exclusive lock on the IR receiver. + + When this option is enabled and another application using the HIDRemote class indicates that it'd like to get exclusive access + to the IR receiver itself while your application is having it, your application's instance of HIDRemote automatically stops and + signals the other application that it can now get exclusive access. When the other application's HIDRemote instance no longer uses + the IR receiver exclusively, it lets your application know so that it can recover its exclusive lock. + + This option is disabled by default. Unless you have special needs, you really should use the kHIDRemoteModeExclusiveAuto mode for + best compatibility with other applications. +*/ +- (void)setExclusiveLockLendingEnabled:(BOOL)newExclusiveLockLendingEnabled; +- (BOOL)exclusiveLockLendingEnabled; + +#pragma mark -- PRIVATE: HID Event handling -- +- (void)_handleButtonCode:(HIDRemoteButtonCode)buttonCode isPressed:(BOOL)isPressed hidAttribsDict:(NSMutableDictionary *)hidAttribsDict; +- (void)_sendButtonCode:(HIDRemoteButtonCode)buttonCode isPressed:(BOOL)isPressed; +- (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; +- (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; + +#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; + +@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; + +#pragma mark -- Distributed notifications -- +extern NSString *kHIDRemoteDNHIDRemotePing; +extern NSString *kHIDRemoteDNHIDRemoteRetry; +extern NSString *kHIDRemoteDNHIDRemoteStatus; + +#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; + +#pragma mark -- Driver compatibility flags -- +typedef enum +{ + kHIDRemoteCompatibilityFlagsStandardHIDRemoteDevice = 1L, +} HIDRemoteCompatibilityFlags; diff --git a/tools/EventClients/Clients/OSXRemote/HIDRemote.m b/tools/EventClients/Clients/OSXRemote/HIDRemote.m new file mode 100644 index 0000000000..f8e9b9f953 --- /dev/null +++ b/tools/EventClients/Clients/OSXRemote/HIDRemote.m @@ -0,0 +1,1544 @@ +// +// HIDRemote.m +// HIDRemote V1.0 +// +// Created by Felix Schwarz on 06.04.07. +// Copyright 2007-2009 IOSPIRIT GmbH. All rights reserved. +// +// ** LICENSE ************************************************************************* +// +// Copyright (c) 2007-2009 IOSPIRIT GmbH +// 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. +// +// ************************************************************************************ + +#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); + +// Shared HIDRemote instance +static HIDRemote *sHIDRemote = nil; + +@implementation HIDRemote + +#pragma mark -- Init, dealloc & shared instance -- + ++ (HIDRemote *)sharedHIDRemote +{ + if (!sHIDRemote) + { + sHIDRemote = [[HIDRemote alloc] init]; + } + + return (sHIDRemote); +} + +- (id)init +{ + if (self = [super init]) + { + // Detect application becoming active/inactive + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_appStatusChanged:) name:NSApplicationDidBecomeActiveNotification object:[NSApplication sharedApplication]]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_appStatusChanged:) name:NSApplicationWillResignActiveNotification object:[NSApplication sharedApplication]]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_appStatusChanged:) name:NSApplicationWillTerminateNotification object:[NSApplication sharedApplication]]; + + // Handle distributed notifications + [[NSDistributedNotificationCenter defaultCenter] addObserver:self selector:@selector(_handleNotifications:) name:kHIDRemoteDNHIDRemotePing object:nil]; + [[NSDistributedNotificationCenter defaultCenter] addObserver:self selector:@selector(_handleNotifications:) name:kHIDRemoteDNHIDRemoteRetry object:[NSString stringWithFormat:@"%d", getpid()]]; + + // 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 + _secureEventInputWorkAround = YES; + _statusSecureEventInputWorkAroundEnabled = NO; + + // Initialize instance variables + _lastSeenRemoteID = -1; + _unusedButtonCodes = [[NSMutableArray alloc] init]; + _exclusiveLockLending = NO; + } + + return (self); +} + +- (void)dealloc +{ + [[NSNotificationCenter defaultCenter] removeObserver:self name:NSApplicationWillTerminateNotification object:[NSApplication sharedApplication]]; + [[NSNotificationCenter defaultCenter] removeObserver:self name:NSApplicationWillResignActiveNotification object:[NSApplication sharedApplication]]; + [[NSNotificationCenter defaultCenter] removeObserver:self name:NSApplicationDidBecomeActiveNotification object:[NSApplication sharedApplication]]; + + [[NSDistributedNotificationCenter defaultCenter] removeObserver:self name:kHIDRemoteDNHIDRemotePing object:nil]; + [[NSDistributedNotificationCenter defaultCenter] removeObserver:self name:kHIDRemoteDNHIDRemoteRetry object:[NSString stringWithFormat:@"%d", getpid()]]; + + [self stopRemoteControl]; + + [self setExclusiveLockLendingEnabled:NO]; + + [self setDelegate:nil]; + + [_unusedButtonCodes release]; + _unusedButtonCodes = 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 || !masterPort) { 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. + // + // IMPORTANT: + // We do not include later OS releases here, because the issue may be fixed in these. + // We simply don't know at this point. Updated sourcecode will be available at + // candelair.com as necessary. + + switch (remoteMode) + { + case kHIDRemoteModeExclusive: + case kHIDRemoteModeExclusiveAuto: + if (![self isCandelairInstalled]) + { + return (YES); + } + break; + } + break; + } + } + + return (NO); +} + +#pragma mark -- PUBLIC: Interface / API -- +- (BOOL)startRemoteControl:(HIDRemoteMode)hidRemoteMode +{ + if ((_mode == kHIDRemoteModeNone) && (hidRemoteMode != kHIDRemoteModeNone)) + { + kern_return_t kernReturn; + CFMutableDictionaryRef matchDict=NULL; + + // Work around a locking issue introduced with Security Update 2008-004 / 10.4.9 and beyond + if ((_mode != kHIDRemoteModeShared) && (_secureEventInputWorkAround)) + { + if (!_statusSecureEventInputWorkAroundEnabled) + { + EnableSecureEventInput(); + + // Keep track of our use of EnableSecureEventInput()/DisableSecureEventInput() to avoid count "leaks" + _statusSecureEventInputWorkAroundEnabled = YES; + } + } + + 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)) + { + CFRunLoopAddSource( CFRunLoopGetCurrent(), + _notifyRLSource, + kCFRunLoopCommonModes); + } + 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) { 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) + { + CFRelease(matchDict); + matchDict = NULL; + } + + [self stopRemoteControl]; + } + + return (NO); +} + +- (void)stopRemoteControl +{ + _autoRecover = NO; + + if (_autoRecoveryTimer) + { + [_autoRecoveryTimer invalidate]; + [_autoRecoveryTimer release]; + _autoRecoveryTimer = nil; + } + + if (_serviceAttribMap) + { + NSDictionary *cloneDict = [[NSDictionary alloc] initWithDictionary:_serviceAttribMap]; + + if (cloneDict) + { + NSEnumerator *mapKeyEnum = [cloneDict keyEnumerator]; + NSNumber *serviceValue; + + while (serviceValue = [mapKeyEnum nextObject]) + { + [self _destructService:(io_object_t)[serviceValue unsignedIntValue]]; + }; + + [cloneDict release]; + cloneDict = nil; + } + + [_serviceAttribMap release]; + _serviceAttribMap = nil; + } + + if (_matchingServicesIterator) + { + IOObjectRelease((io_object_t) _matchingServicesIterator); + _matchingServicesIterator = 0; + } + + if (_notifyRLSource) + { + CFRunLoopSourceInvalidate(_notifyRLSource); + + _notifyRLSource = NULL; + } + + if (_notifyPort) + { + IONotificationPortDestroy(_notifyPort); + _notifyPort = NULL; + } + + if (_masterPort) + { + mach_port_deallocate(mach_task_self(), _masterPort); + } + + if (_statusSecureEventInputWorkAroundEnabled) + { + DisableSecureEventInput(); + _statusSecureEventInputWorkAroundEnabled = NO; + } + + [self _postStatusWithAction:kHIDRemoteDNStatusActionStop]; + + [_returnToPID release]; + _returnToPID = nil; + + _mode = kHIDRemoteModeNone; +} + +- (BOOL)isStarted +{ + return (_mode != kHIDRemoteModeNone); +} + +- (unsigned)activeRemoteControlCount +{ + return ([_serviceAttribMap count]); +} + +- (SInt32)lastSeenRemoteControlID +{ + return (_lastSeenRemoteID); +} + +- (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; + + if ([self isStarted]) + { + if (_mode != kHIDRemoteModeShared) + { + // Changes go live immediately + if (_secureEventInputWorkAround && !_statusSecureEventInputWorkAroundEnabled) + { + EnableSecureEventInput(); + _statusSecureEventInputWorkAroundEnabled = YES; + } + + if (!_secureEventInputWorkAround && _statusSecureEventInputWorkAroundEnabled) + { + DisableSecureEventInput(); + _statusSecureEventInputWorkAroundEnabled = NO; + } + } + } +} + +- (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); +} + +#pragma mark -- PRIVATE: Application becomes active / inactive handling for kHIDRemoteModeExclusiveAuto -- +- (void)_appStatusChanged:(NSNotification *)notification +{ + if (notification) + { + if (_autoRecoveryTimer) + { + [_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]) + { + if ([self isStarted]) + { + [self stopRemoteControl]; + } + } + } +} + +- (void)_delayedAutoRecovery:(NSTimer *)aTimer +{ + [_autoRecoveryTimer invalidate]; + [_autoRecoveryTimer release]; + _autoRecoveryTimer = nil; + + if (_autoRecover) + { + [self startRemoteControl:kHIDRemoteModeExclusiveAuto]; + _autoRecover = NO; + } +} + + +#pragma mark -- PRIVATE: Distributed notifiations handling -- +- (void)_postStatusWithAction:(NSString *)action +{ + [[NSDistributedNotificationCenter defaultCenter] postNotificationName:kHIDRemoteDNHIDRemoteStatus + object:[NSString stringWithFormat:@"%d",getpid()] + userInfo:[NSDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithInt:1], kHIDRemoteDNStatusHIDRemoteVersionKey, + [NSNumber numberWithUnsignedInt:(unsigned int)getpid()], kHIDRemoteDNStatusPIDKey, + [NSNumber numberWithInt:(int)_mode], kHIDRemoteDNStatusModeKey, + [NSNumber numberWithUnsignedInt:(unsigned int)[self activeRemoteControlCount]], kHIDRemoteDNStatusRemoteControlCountKey, + ((_unusedButtonCodes!=nil) ? _unusedButtonCodes : [NSArray array]), kHIDRemoteDNStatusUnusedButtonCodesKey, + action, kHIDRemoteDNStatusActionKey, + [[NSBundle mainBundle] bundleIdentifier], (NSString *)kCFBundleIdentifierKey, + _returnToPID, kHIDRemoteDNStatusReturnToPIDKey, + nil] + deliverImmediately:YES + ]; +} + +- (void)_handleNotifications:(NSNotification *)notification +{ + NSString *notificationName; + + if ((notification!=nil) && ((notificationName = [notification name]) != nil)) + { + if ([notificationName isEqual:kHIDRemoteDNHIDRemotePing]) + { + [self _postStatusWithAction:kHIDRemoteDNStatusActionUpdate]; + } + + if ([notificationName isEqual:kHIDRemoteDNHIDRemoteRetry]) + { + if ([self isStarted]) + { + BOOL retry = YES; + + if (([self delegate] != nil) && + ([[self delegate] respondsToSelector:@selector(hidRemote:shouldRetryExclusiveLockWithInfo:)])) + { + retry = [[self delegate] hidRemote:self shouldRetryExclusiveLockWithInfo:[notification userInfo]]; + } + + if (retry) + { + HIDRemoteMode restartInMode = _mode; + + if (restartInMode != kHIDRemoteModeNone) + { + [self stopRemoteControl]; + + [_returnToPID release]; + [self startRemoteControl:restartInMode]; + + _returnToPID = [[[notification userInfo] objectForKey:kHIDRemoteDNStatusPIDKey] retain]; + } + } + } + } + + if (_exclusiveLockLending) + { + if ([notificationName isEqual:kHIDRemoteDNHIDRemoteStatus]) + { + NSString *action; + + if ((action = [[notification userInfo] objectForKey:kHIDRemoteDNStatusActionKey]) != nil) + { + if (_mode==kHIDRemoteModeExclusive) + { + if ([action isEqual:kHIDRemoteDNStatusActionStart]) + { + NSNumber *originPID = [[notification userInfo] objectForKey:kHIDRemoteDNStatusPIDKey]; + + if ([originPID intValue] != getpid()) + { + if (([self delegate] != nil) && + ([[self delegate] respondsToSelector:@selector(hidRemote:lendExclusiveLockToApplicationWithInfo:)])) + { + if ([[self delegate] hidRemote:self lendExclusiveLockToApplicationWithInfo:[notification userInfo]]) + { + [_waitForReturnByPID release]; + _waitForReturnByPID = [originPID retain]; + + if (_waitForReturnByPID != nil) + { + BOOL cachedStatusSecureEventInputWorkAroundEnabled; + + // Workaround for what seems to be a missing lock on the counter in the EnableSecureEventInput() / DisableSecureEventInput() API + cachedStatusSecureEventInputWorkAroundEnabled = _statusSecureEventInputWorkAroundEnabled; + _statusSecureEventInputWorkAroundEnabled = NO; + + [self stopRemoteControl]; + + _statusSecureEventInputWorkAroundEnabled = cachedStatusSecureEventInputWorkAroundEnabled; + + [[NSDistributedNotificationCenter defaultCenter] postNotificationName:kHIDRemoteDNHIDRemoteRetry + object:[NSString stringWithFormat:@"%d", [_waitForReturnByPID intValue]] + userInfo:[NSDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithUnsignedInt:(unsigned int)getpid()], kHIDRemoteDNStatusPIDKey, + [[NSBundle mainBundle] bundleIdentifier], (NSString *)kCFBundleIdentifierKey, + nil] + deliverImmediately:YES]; + } + } + } + } + } + } + + if ((_mode == kHIDRemoteModeNone) && _waitForReturnByPID) + { + if ([action isEqual:kHIDRemoteDNStatusActionStop]) + { + NSNumber *pidNumber, *returnToPIDNumber; + + if (((pidNumber = [[notification userInfo] objectForKey:kHIDRemoteDNStatusPIDKey]) != nil) && + ((returnToPIDNumber = [[notification userInfo] objectForKey:kHIDRemoteDNStatusReturnToPIDKey]) != nil)) + { + if ([pidNumber isEqual:_waitForReturnByPID] && ([returnToPIDNumber intValue] == getpid())) + { + [_waitForReturnByPID release]; + _waitForReturnByPID = nil; + + if (([self delegate] != nil) && + ([[self delegate] respondsToSelector:@selector(hidRemote:exclusiveLockReleasedByApplicationWithInfo:)])) + { + [[self delegate] hidRemote:self exclusiveLockReleasedByApplicationWithInfo:[notification userInfo]]; + } + else + { + [self startRemoteControl:kHIDRemoteModeExclusive]; + } + } + } + } + } + } + } + } + } +} + +#pragma mark -- PRIVATE: Service setup and destruction -- +- (BOOL)_prematchService:(io_object_t)service +{ + BOOL serviceMatches = NO; + NSString *ioClass; + NSNumber *candelairHIDRemoteCompatibilityMask; + + if (service != 0) + { + // IOClass matching + if ((ioClass = (NSString *)IORegistryEntryCreateCFProperty((io_registry_entry_t)service, + CFSTR(kIOClassKey), + kCFAllocatorDefault, + 0)) != nil) + { + // Match on Apple's AppleIRController + if ([ioClass isEqual:@"AppleIRController"]) + { + CFTypeRef candelairHIDRemoteCompatibilityDevice; + + serviceMatches = YES; + + if ((candelairHIDRemoteCompatibilityDevice = IORegistryEntryCreateCFProperty((io_registry_entry_t)service, CFSTR("CandelairHIDRemoteCompatibilityDevice"), kCFAllocatorDefault, 0)) != NULL) + { + if (CFEqual(kCFBooleanTrue, candelairHIDRemoteCompatibilityDevice)) + { + serviceMatches = NO; + } + + CFRelease (candelairHIDRemoteCompatibilityDevice); + } + } + + // Match on the virtual IOSPIRIT IR Controller + if ([ioClass isEqual:@"IOSPIRITIRController"]) + { + serviceMatches = YES; + } + + CFRelease((CFTypeRef)ioClass); + } + + // Match on services that claim compatibility with the HID Remote class (Candelair or third-party) by having a property of CandelairHIDRemoteCompatibilityMask = 1 <Type: Number> + if ((candelairHIDRemoteCompatibilityMask = (NSNumber *)IORegistryEntryCreateCFProperty((io_registry_entry_t)service, CFSTR("CandelairHIDRemoteCompatibilityMask"), kCFAllocatorDefault, 0)) != nil) + { + if ([candelairHIDRemoteCompatibilityMask isKindOfClass:[NSNumber class]]) + { + if ([candelairHIDRemoteCompatibilityMask unsignedIntValue] & kHIDRemoteCompatibilityFlagsStandardHIDRemoteDevice) + { + serviceMatches = YES; + } + else + { + serviceMatches = NO; + } + } + + CFRelease((CFTypeRef)candelairHIDRemoteCompatibilityMask); + } + } + + if (([self delegate]!=nil) && + ([[self delegate] respondsToSelector:@selector(hidRemote:inspectNewHardwareWithService:prematchResult:)])) + { + serviceMatches = [((NSObject <HIDRemoteDelegate> *)[self delegate]) hidRemote:self inspectNewHardwareWithService:service prematchResult:serviceMatches]; + } + + return (serviceMatches); +} + +- (BOOL)_setupService:(io_object_t)service +{ + kern_return_t kernResult; + IOReturn returnCode; + HRESULT hResult; + SInt32 score; + BOOL opened = NO, queueStarted = NO; + IOHIDDeviceInterface122 **hidDeviceInterface = NULL; + IOCFPlugInInterface **cfPluginInterface = NULL; + IOHIDQueueInterface **hidQueueInterface = NULL; + io_object_t serviceNotification = 0; + CFRunLoopSourceRef queueEventSource = NULL; + NSMutableDictionary *hidAttribsDict = nil; + CFArrayRef hidElements = NULL; + NSError *error = nil; + UInt32 errorCode = 0; + + if (![self _prematchService:service]) + { + return (NO); + } + + do + { + // Create a plugin interface .. + kernResult = IOCreatePlugInInterfaceForService( service, + kIOHIDDeviceUserClientTypeID, + kIOCFPlugInInterfaceID, + &cfPluginInterface, + &score); + + if (kernResult != kIOReturnSuccess) + { + error = [NSError errorWithDomain:NSMachErrorDomain code:kernResult userInfo:nil]; + errorCode = 1; + break; + } + + + // .. use it to get the HID interface .. + hResult = (*cfPluginInterface)->QueryInterface( cfPluginInterface, + CFUUIDGetUUIDBytes(kIOHIDDeviceInterfaceID122), + (LPVOID)&hidDeviceInterface); + + if ((hResult!=S_OK) || (hidDeviceInterface==NULL)) + { + error = [NSError errorWithDomain:NSMachErrorDomain code:hResult userInfo:nil]; + errorCode = 2; + break; + } + + + // .. then open it .. + switch (_mode) + { + case kHIDRemoteModeShared: + hResult = (*hidDeviceInterface)->open(hidDeviceInterface, kIOHIDOptionsTypeNone); + break; + + case kHIDRemoteModeExclusive: + case kHIDRemoteModeExclusiveAuto: + hResult = (*hidDeviceInterface)->open(hidDeviceInterface, kIOHIDOptionsTypeSeizeDevice); + break; + + default: + goto cleanUp; // Ugh! But there are no "double breaks" available in C AFAIK .. + break; + } + + if (hResult!=S_OK) + { + error = [NSError errorWithDomain:NSMachErrorDomain code:hResult userInfo:nil]; + errorCode = 3; + break; + } + + opened = YES; + + // .. query the HID elements .. + returnCode = (*hidDeviceInterface)->copyMatchingElements(hidDeviceInterface, + NULL, + &hidElements); + if ((returnCode != kIOReturnSuccess) || (hidElements==NULL)) + { + error = [NSError errorWithDomain:NSMachErrorDomain code:returnCode userInfo:nil]; + errorCode = 4; + + break; + } + + // Setup an event queue for HID events! + hidQueueInterface = (*hidDeviceInterface)->allocQueue(hidDeviceInterface); + if (hidQueueInterface == NULL) + { + error = [NSError errorWithDomain:NSMachErrorDomain code:kIOReturnError userInfo:nil]; + errorCode = 5; + + break; + } + + returnCode = (*hidQueueInterface)->create(hidQueueInterface, 0, 32); + if ((returnCode != kIOReturnSuccess) || (hidElements==NULL)) + { + error = [NSError errorWithDomain:NSMachErrorDomain code:returnCode userInfo:nil]; + errorCode = 6; + + break; + } + + + // Setup of attributes stored for this HID device + hidAttribsDict = [[NSMutableDictionary alloc] initWithObjectsAndKeys: + [NSValue valueWithPointer:(const void *)cfPluginInterface], kHIDRemoteCFPluginInterface, + [NSValue valueWithPointer:(const void *)hidDeviceInterface], kHIDRemoteHIDDeviceInterface, + [NSValue valueWithPointer:(const void *)hidQueueInterface], kHIDRemoteHIDQueueInterface, + nil]; + + { + UInt32 i, hidElementCnt = CFArrayGetCount(hidElements); + NSMutableDictionary *cookieButtonCodeLUT = [[NSMutableDictionary alloc] init]; + NSMutableDictionary *cookieCount = [[NSMutableDictionary alloc] init]; + + if ((cookieButtonCodeLUT==nil) || (cookieCount==nil)) + { + [cookieButtonCodeLUT release]; + cookieButtonCodeLUT = nil; + + [cookieCount release]; + cookieCount = nil; + + error = [NSError errorWithDomain:NSMachErrorDomain code:kIOReturnError userInfo:nil]; + errorCode = 7; + + break; + } + + // Analyze the HID elements and find matching elements + for (i=0;i<hidElementCnt;i++) + { + CFDictionaryRef hidDict; + NSNumber *usage, *usagePage, *cookie; + HIDRemoteButtonCode buttonCode = kHIDRemoteButtonCodeNone; + + hidDict = CFArrayGetValueAtIndex(hidElements, i); + + usage = (NSNumber *) CFDictionaryGetValue(hidDict, CFSTR(kIOHIDElementUsageKey)); + usagePage = (NSNumber *) CFDictionaryGetValue(hidDict, CFSTR(kIOHIDElementUsagePageKey)); + cookie = (NSNumber *) CFDictionaryGetValue(hidDict, CFSTR(kIOHIDElementCookieKey)); + + if (usage && usagePage && cookie) + { + // Find the button codes for the ID combos + switch ([usagePage unsignedIntValue]) + { + case kHIDPage_Consumer: + switch ([usage unsignedIntValue]) + { + case kHIDUsage_Csmr_Rewind: + buttonCode = kHIDRemoteButtonCodeLeftHold; + break; + + case kHIDUsage_Csmr_FastForward: + buttonCode = kHIDRemoteButtonCodeRightHold; + break; + + case kHIDUsage_Csmr_Menu: + buttonCode = kHIDRemoteButtonCodeMenuHold; + break; + } + break; + + case kHIDPage_GenericDesktop: + switch ([usage unsignedIntValue]) + { + case kHIDUsage_GD_SystemAppMenu: + buttonCode = kHIDRemoteButtonCodeMenu; + break; + + case kHIDUsage_GD_SystemMenu: + buttonCode = kHIDRemoteButtonCodePlayPause; + break; + + case kHIDUsage_GD_SystemMenuRight: + buttonCode = kHIDRemoteButtonCodeRight; + break; + + case kHIDUsage_GD_SystemMenuLeft: + buttonCode = kHIDRemoteButtonCodeLeft; + break; + + case kHIDUsage_GD_SystemMenuUp: + buttonCode = kHIDRemoteButtonCodePlus; + break; + + case kHIDUsage_GD_SystemMenuDown: + buttonCode = kHIDRemoteButtonCodeMinus; + break; + } + break; + + case 0x06: /* Resered */ + switch ([usage unsignedIntValue]) + { + case 0x22: + buttonCode = kHIDRemoteButtonCodeIDChanged; + break; + } + break; + + case 0xff01: /* Vendor specific */ + switch ([usage unsignedIntValue]) + { + case 0x23: + buttonCode = kHIDRemoteButtonCodePlayPauseHold; + break; + + #ifdef _HIDREMOTE_EXTENSIONS + #define _HIDREMOTE_EXTENSIONS_SECTION 2 + #include "HIDRemoteAdditions.h" + #undef _HIDREMOTE_EXTENSIONS_SECTION + #endif /* _HIDREMOTE_EXTENSIONS */ + } + break; + } + + // Did record match? + if (buttonCode != kHIDRemoteButtonCodeNone) + { + NSString *pairString = [[NSString alloc] initWithFormat:@"%u_%u", [usagePage unsignedIntValue], [usage unsignedIntValue]]; + NSNumber *buttonCodeNumber = [[NSNumber alloc] initWithUnsignedInt:(unsigned int)buttonCode]; + + [cookieCount setObject:buttonCodeNumber forKey:pairString]; + [cookieButtonCodeLUT setObject:buttonCodeNumber forKey:cookie]; + + (*hidQueueInterface)->addElement(hidQueueInterface, + (IOHIDElementCookie) [cookie unsignedIntValue], + 0); + + [buttonCodeNumber release]; + [pairString release]; + } + } + } + + // Compare number of *unique* matches (thus the cookieCount dictionary) with required minimum + if ([cookieCount count] < 10) + { + [cookieButtonCodeLUT release]; + cookieButtonCodeLUT = nil; + + [cookieCount release]; + cookieCount = nil; + + error = [NSError errorWithDomain:NSMachErrorDomain code:kIOReturnError userInfo:nil]; + errorCode = 8; + + break; + } + + [hidAttribsDict setObject:cookieButtonCodeLUT forKey:kHIDRemoteCookieButtonCodeLUT]; + + [cookieButtonCodeLUT release]; + cookieButtonCodeLUT = nil; + + [cookieCount release]; + cookieCount = nil; + } + + // Finish setup of IOHIDQueueInterface with CFRunLoop + returnCode = (*hidQueueInterface)->createAsyncEventSource(hidQueueInterface, &queueEventSource); + if ((returnCode != kIOReturnSuccess) || (queueEventSource == NULL)) + { + error = [NSError errorWithDomain:NSMachErrorDomain code:returnCode userInfo:nil]; + errorCode = 9; + break; + } + + returnCode = (*hidQueueInterface)->setEventCallout(hidQueueInterface, HIDEventCallback, (void *)((intptr_t)service), (void *)self); + if (returnCode != kIOReturnSuccess) + { + error = [NSError errorWithDomain:NSMachErrorDomain code:returnCode userInfo:nil]; + errorCode = 10; + break; + } + + CFRunLoopAddSource( CFRunLoopGetCurrent(), + queueEventSource, + kCFRunLoopCommonModes); + [hidAttribsDict setObject:[NSValue valueWithPointer:(const void *)queueEventSource] forKey:kHIDRemoteCFRunLoopSource]; + + returnCode = (*hidQueueInterface)->start(hidQueueInterface); + if (returnCode != kIOReturnSuccess) + { + error = [NSError errorWithDomain:NSMachErrorDomain code:returnCode userInfo:nil]; + errorCode = 11; + break; + } + + queueStarted = YES; + + // Setup device notifications + returnCode = IOServiceAddInterestNotification( _notifyPort, + service, + kIOGeneralInterest, + ServiceNotificationCallback, + self, + &serviceNotification); + if ((returnCode != kIOReturnSuccess) || (serviceNotification==0)) + { + error = [NSError errorWithDomain:NSMachErrorDomain code:returnCode userInfo:nil]; + errorCode = 12; + break; + } + + [hidAttribsDict setObject:[NSNumber numberWithUnsignedInt:(unsigned int)serviceNotification] forKey:kHIDRemoteServiceNotification]; + + // Retain service + if (IOObjectRetain(service) != kIOReturnSuccess) + { + error = [NSError errorWithDomain:NSMachErrorDomain code:kIOReturnError userInfo:nil]; + errorCode = 13; + break; + } + + [hidAttribsDict setObject:[NSNumber numberWithUnsignedInt:(unsigned int)service] forKey:kHIDRemoteService]; + + // Get some (somewhat optional) infos on the device + { + CFStringRef product, manufacturer, transport; + + if ((product = IORegistryEntryCreateCFProperty( (io_registry_entry_t)service, + (CFStringRef) @"Product", + kCFAllocatorDefault, + 0)) != NULL) + { + if (CFGetTypeID(product) == CFStringGetTypeID()) + { + [hidAttribsDict setObject:(NSString *)product forKey:kHIDRemoteProduct]; + } + + CFRelease(product); + } + + if ((manufacturer = IORegistryEntryCreateCFProperty( (io_registry_entry_t)service, + (CFStringRef) @"Manufacturer", + kCFAllocatorDefault, + 0)) != NULL) + { + if (CFGetTypeID(manufacturer) == CFStringGetTypeID()) + { + [hidAttribsDict setObject:(NSString *)manufacturer forKey:kHIDRemoteManufacturer]; + } + + CFRelease(manufacturer); + } + + if ((transport = IORegistryEntryCreateCFProperty( (io_registry_entry_t)service, + (CFStringRef) @"Transport", + kCFAllocatorDefault, + 0)) != NULL) + { + if (CFGetTypeID(transport) == CFStringGetTypeID()) + { + [hidAttribsDict setObject:(NSString *)transport forKey:kHIDRemoteTransport]; + } + + CFRelease(transport); + } + } + + // Add it to the serviceAttribMap + [_serviceAttribMap setObject:hidAttribsDict forKey:[NSNumber numberWithUnsignedInt:(unsigned int)service]]; + + // And we're done with setup .. + if (([self delegate]!=nil) && + ([[self delegate] respondsToSelector:@selector(hidRemote:foundNewHardwareWithAttributes:)])) + { + [((NSObject <HIDRemoteDelegate> *)[self delegate]) hidRemote:self foundNewHardwareWithAttributes:hidAttribsDict]; + } + + [hidAttribsDict release]; + hidAttribsDict = nil; + + return(YES); + + }while(0); + + cleanUp: + + if (([self delegate]!=nil) && + ([[self delegate] respondsToSelector:@selector(hidRemote:failedNewHardwareWithError:)])) + { + if (error) + { + error = [NSError errorWithDomain:[error domain] + code:[error code] + userInfo:[NSDictionary dictionaryWithObject:[NSNumber numberWithInt:errorCode] forKey:@"InternalErrorCode"] + ]; + } + + [((NSObject <HIDRemoteDelegate> *)[self delegate]) hidRemote:self failedNewHardwareWithError:error]; + } + + // An error occured or this device is not of interest .. cleanup .. + if (serviceNotification) + { + IOObjectRelease(serviceNotification); + serviceNotification = 0; + } + + if (queueEventSource) + { + CFRunLoopSourceInvalidate(queueEventSource); + queueEventSource=NULL; + } + + if (hidQueueInterface) + { + if (queueStarted) + { + (*hidQueueInterface)->stop(hidQueueInterface); + } + (*hidQueueInterface)->dispose(hidQueueInterface); + (*hidQueueInterface)->Release(hidQueueInterface); + hidQueueInterface = NULL; + } + + if (hidAttribsDict) + { + [hidAttribsDict release]; + hidAttribsDict = nil; + } + + if (hidElements) + { + CFRelease(hidElements); + hidElements = NULL; + } + + if (hidDeviceInterface) + { + if (opened) + { + (*hidDeviceInterface)->close(hidDeviceInterface); + } + (*hidDeviceInterface)->Release(hidDeviceInterface); + // opened = NO; + hidDeviceInterface = NULL; + } + + if (cfPluginInterface) + { + IODestroyPlugInInterface(cfPluginInterface); + cfPluginInterface = NULL; + } + + return (NO); +} + +- (void)_destructService:(io_object_t)service +{ + NSNumber *serviceValue; + NSMutableDictionary *serviceDict = NULL; + + if ((serviceValue = [NSNumber numberWithUnsignedInt:(unsigned int)service]) == nil) + { + return; + } + + serviceDict = [_serviceAttribMap objectForKey:serviceValue]; + + if (serviceDict) + { + IOHIDDeviceInterface122 **hidDeviceInterface = NULL; + IOCFPlugInInterface **cfPluginInterface = NULL; + IOHIDQueueInterface **hidQueueInterface = NULL; + io_object_t serviceNotification = 0; + CFRunLoopSourceRef queueEventSource = NULL; + io_object_t theService = 0; + NSMutableDictionary *cookieButtonMap = nil; + NSTimer *simulateHoldTimer = nil; + + serviceNotification = (io_object_t) ([serviceDict objectForKey:kHIDRemoteServiceNotification] ? [[serviceDict objectForKey:kHIDRemoteServiceNotification] unsignedIntValue] : 0); + theService = (io_object_t) ([serviceDict objectForKey:kHIDRemoteService] ? [[serviceDict objectForKey:kHIDRemoteService] unsignedIntValue] : 0); + queueEventSource = (CFRunLoopSourceRef) ([serviceDict objectForKey:kHIDRemoteCFRunLoopSource] ? [[serviceDict objectForKey:kHIDRemoteCFRunLoopSource] pointerValue] : NULL); + hidQueueInterface = (IOHIDQueueInterface **) ([serviceDict objectForKey:kHIDRemoteHIDQueueInterface] ? [[serviceDict objectForKey:kHIDRemoteHIDQueueInterface] pointerValue] : NULL); + hidDeviceInterface = (IOHIDDeviceInterface122 **) ([serviceDict objectForKey:kHIDRemoteHIDDeviceInterface] ? [[serviceDict objectForKey:kHIDRemoteHIDDeviceInterface] pointerValue] : NULL); + cfPluginInterface = (IOCFPlugInInterface **) ([serviceDict objectForKey:kHIDRemoteCFPluginInterface] ? [[serviceDict objectForKey:kHIDRemoteCFPluginInterface] pointerValue] : NULL); + cookieButtonMap = (NSMutableDictionary *) [serviceDict objectForKey:kHIDRemoteCookieButtonCodeLUT]; + simulateHoldTimer = (NSTimer *) [serviceDict objectForKey:kHIDRemoteSimulateHoldEventsTimer]; + + [serviceDict retain]; + [_serviceAttribMap removeObjectForKey:serviceValue]; + + if (([self delegate]!=nil) && + ([[self delegate] respondsToSelector:@selector(hidRemote:releasedHardwareWithAttributes:)])) + { + [((NSObject <HIDRemoteDelegate> *)[self delegate]) hidRemote:self releasedHardwareWithAttributes:serviceDict]; + } + + if (simulateHoldTimer) + { + [simulateHoldTimer invalidate]; + } + + if (serviceNotification) + { + IOObjectRelease(serviceNotification); + } + + if (queueEventSource) + { + CFRunLoopRemoveSource( CFRunLoopGetCurrent(), + queueEventSource, + kCFRunLoopCommonModes); + } + + if (hidQueueInterface && cookieButtonMap) + { + NSEnumerator *cookieEnum = [cookieButtonMap keyEnumerator]; + NSNumber *cookie; + + while (cookie = [cookieEnum nextObject]) + { + if ((*hidQueueInterface)->hasElement(hidQueueInterface, (IOHIDElementCookie) [cookie unsignedIntValue])) + { + (*hidQueueInterface)->removeElement(hidQueueInterface, + (IOHIDElementCookie) [cookie unsignedIntValue]); + } + }; + } + + if (hidQueueInterface) + { + (*hidQueueInterface)->stop(hidQueueInterface); + (*hidQueueInterface)->dispose(hidQueueInterface); + (*hidQueueInterface)->Release(hidQueueInterface); + } + + if (hidDeviceInterface) + { + (*hidDeviceInterface)->close(hidDeviceInterface); + (*hidDeviceInterface)->Release(hidDeviceInterface); + } + + if (cfPluginInterface) + { + IODestroyPlugInInterface(cfPluginInterface); + } + + if (theService) + { + IOObjectRelease(theService); + } + + [serviceDict release]; + } +} + + +#pragma mark -- PRIVATE: HID Event handling -- +- (void)_simulateHoldEvent:(NSTimer *)aTimer +{ + NSMutableDictionary *hidAttribsDict; + NSTimer *shTimer; + NSNumber *shButtonCode; + + if ((hidAttribsDict = (NSMutableDictionary *)[aTimer userInfo]) != nil) + { + if (((shTimer = [hidAttribsDict objectForKey:kHIDRemoteSimulateHoldEventsTimer]) != nil) && + ((shButtonCode = [hidAttribsDict objectForKey:kHIDRemoteSimulateHoldEventsOriginButtonCode]) != nil)) + { + [shTimer invalidate]; + [hidAttribsDict removeObjectForKey:kHIDRemoteSimulateHoldEventsTimer]; + + [self _sendButtonCode:(((HIDRemoteButtonCode)[shButtonCode unsignedIntValue])|kHIDRemoteButtonCodeHoldMask) isPressed:YES]; + } + } +} + +- (void)_handleButtonCode:(HIDRemoteButtonCode)buttonCode isPressed:(BOOL)isPressed hidAttribsDict:(NSMutableDictionary *)hidAttribsDict +{ + switch (buttonCode) + { + case kHIDRemoteButtonCodeIDChanged: + // Do nothing, this is handled seperately + break; + + case kHIDRemoteButtonCodePlus: + case kHIDRemoteButtonCodeMinus: + if (_simulateHoldEvents) + { + NSTimer *shTimer = nil; + NSNumber *shButtonCode = nil; + + [[hidAttribsDict objectForKey:kHIDRemoteSimulateHoldEventsTimer] invalidate]; + + if (isPressed) + { + [hidAttribsDict setObject:[NSNumber numberWithUnsignedInt:buttonCode] forKey:kHIDRemoteSimulateHoldEventsOriginButtonCode]; + + if ((shTimer = [[NSTimer alloc] initWithFireDate:[NSDate dateWithTimeIntervalSinceNow:0.7] interval:0.1 target:self selector:@selector(_simulateHoldEvent:) userInfo:hidAttribsDict repeats:NO]) != nil) + { + [hidAttribsDict setObject:shTimer forKey:kHIDRemoteSimulateHoldEventsTimer]; + + // 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)shTimer, kCFRunLoopCommonModes); + + [shTimer release]; + + break; + } + } + else + { + shTimer = [hidAttribsDict objectForKey:kHIDRemoteSimulateHoldEventsTimer]; + shButtonCode = [hidAttribsDict objectForKey:kHIDRemoteSimulateHoldEventsOriginButtonCode]; + + if (shTimer && shButtonCode) + { + [self _sendButtonCode:(HIDRemoteButtonCode)[shButtonCode unsignedIntValue] isPressed:YES]; + [self _sendButtonCode:(HIDRemoteButtonCode)[shButtonCode unsignedIntValue] isPressed:NO]; + } + else + { + if (shButtonCode) + { + [self _sendButtonCode:(((HIDRemoteButtonCode)[shButtonCode unsignedIntValue])|kHIDRemoteButtonCodeHoldMask) isPressed:NO]; + } + } + } + + [hidAttribsDict removeObjectForKey:kHIDRemoteSimulateHoldEventsTimer]; + [hidAttribsDict removeObjectForKey:kHIDRemoteSimulateHoldEventsOriginButtonCode]; + + break; + } + + default: + [self _sendButtonCode:buttonCode isPressed:isPressed]; + break; + } +} + +- (void)_sendButtonCode:(HIDRemoteButtonCode)buttonCode isPressed:(BOOL)isPressed +{ + if (([self delegate]!=nil) && + ([[self delegate] respondsToSelector:@selector(hidRemote:eventWithButton:isPressed:)])) + { + [((NSObject <HIDRemoteDelegate> *)[self delegate]) hidRemote:self eventWithButton:buttonCode isPressed:isPressed]; + } +} + +- (void)_hidEventFor:(io_service_t)hidDevice from:(IOHIDQueueInterface **)interface withResult:(IOReturn)result +{ + NSMutableDictionary *hidAttribsDict = [_serviceAttribMap objectForKey:[NSNumber numberWithUnsignedInt:(unsigned int)hidDevice]]; + + if (hidAttribsDict) + { + IOHIDQueueInterface **queueInterface = NULL; + + queueInterface = [[hidAttribsDict objectForKey:kHIDRemoteHIDQueueInterface] pointerValue]; + + if (interface == queueInterface) + { + NSNumber *lastButtonPressedNumber = nil; + HIDRemoteButtonCode lastButtonPressed = kHIDRemoteButtonCodeNone; + NSMutableDictionary *cookieButtonMap = nil; + + cookieButtonMap = [hidAttribsDict objectForKey:kHIDRemoteCookieButtonCodeLUT]; + + if ((lastButtonPressedNumber = [hidAttribsDict objectForKey:kHIDRemoteLastButtonPressed]) != nil) + { + lastButtonPressed = [lastButtonPressedNumber unsignedIntValue]; + } + + while (result == kIOReturnSuccess) + { + IOHIDEventStruct hidEvent; + AbsoluteTime supportedTime = { 0,0 }; + + result = (*queueInterface)->getNextEvent( queueInterface, + &hidEvent, + supportedTime, + 0); + + if (result == kIOReturnSuccess) + { + NSNumber *buttonCodeNumber = [cookieButtonMap objectForKey:[NSNumber numberWithUnsignedInt:(unsigned int) hidEvent.elementCookie]]; + + if (buttonCodeNumber) + { + HIDRemoteButtonCode buttonCode = [buttonCodeNumber unsignedIntValue]; + + if (hidEvent.value == 0) + { + if (buttonCode == lastButtonPressed) + { + [self _handleButtonCode:lastButtonPressed isPressed:NO hidAttribsDict:hidAttribsDict]; + lastButtonPressed = kHIDRemoteButtonCodeNone; + } + } + + if (hidEvent.value != 0) + { + if (lastButtonPressed != kHIDRemoteButtonCodeNone) + { + [self _handleButtonCode:lastButtonPressed isPressed:NO hidAttribsDict:hidAttribsDict]; + // lastButtonPressed = kHIDRemoteButtonCodeNone; + } + + if (buttonCode == kHIDRemoteButtonCodeIDChanged) + { + if (([self delegate]!=nil) && + ([[self delegate] respondsToSelector:@selector(hidRemote:remoteIDChangedOldID:newID:)])) + { + [((NSObject <HIDRemoteDelegate> *)[self delegate]) hidRemote:self remoteIDChangedOldID:_lastSeenRemoteID newID:hidEvent.value]; + } + + _lastSeenRemoteID = hidEvent.value; + } + + [self _handleButtonCode:buttonCode isPressed:YES hidAttribsDict:hidAttribsDict]; + lastButtonPressed = buttonCode; + } + } + } + }; + + [hidAttribsDict setObject:[NSNumber numberWithUnsignedInt:lastButtonPressed] forKey:kHIDRemoteLastButtonPressed]; + } + } +} + +#pragma mark -- PRIVATE: Notification handling -- +- (void)_serviceMatching:(io_iterator_t)iterator +{ + io_object_t matchingService = 0; + + while (matchingService = IOIteratorNext(iterator)) + { + [self _setupService:matchingService]; + + IOObjectRelease(matchingService); + }; +} + +- (void)_serviceNotificationFor:(io_service_t)service messageType:(natural_t)messageType messageArgument:(void *)messageArgument +{ + if (messageType == kIOMessageServiceIsTerminated) + { + [self _destructService:service]; + } +} + +@end + +#pragma mark -- PRIVATE: IOKitLib Callbacks -- + +static void HIDEventCallback( void * target, + IOReturn result, + void * refCon, + void * sender) +{ + HIDRemote *hidRemote = (HIDRemote *)refCon; + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + [hidRemote _hidEventFor:(io_service_t)((intptr_t)target) from:(IOHIDQueueInterface**)sender withResult:(IOReturn)result]; + + [pool release]; +} + + +static void ServiceMatchingCallback( void *refCon, + io_iterator_t iterator) +{ + HIDRemote *hidRemote = (HIDRemote *)refCon; + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + [hidRemote _serviceMatching:iterator]; + + [pool release]; +} + +static void ServiceNotificationCallback(void * refCon, + io_service_t service, + natural_t messageType, + void * messageArgument) +{ + HIDRemote *hidRemote = (HIDRemote *)refCon; + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + [hidRemote _serviceNotificationFor:service + messageType:messageType + messageArgument:messageArgument]; + + [pool release]; +} + +// Attribute dictionary keys +NSString *kHIDRemoteCFPluginInterface = @"CFPluginInterface"; +NSString *kHIDRemoteHIDDeviceInterface = @"HIDDeviceInterface"; +NSString *kHIDRemoteCookieButtonCodeLUT = @"CookieButtonCodeLUT"; +NSString *kHIDRemoteHIDQueueInterface = @"HIDQueueInterface"; +NSString *kHIDRemoteServiceNotification = @"ServiceNotification"; +NSString *kHIDRemoteCFRunLoopSource = @"CFRunLoopSource"; +NSString *kHIDRemoteLastButtonPressed = @"LastButtonPressed"; +NSString *kHIDRemoteService = @"Service"; +NSString *kHIDRemoteSimulateHoldEventsTimer = @"SimulateHoldEventsTimer"; +NSString *kHIDRemoteSimulateHoldEventsOriginButtonCode = @"SimulateHoldEventsOriginButtonCode"; + +NSString *kHIDRemoteManufacturer = @"Manufacturer"; +NSString *kHIDRemoteProduct = @"Product"; +NSString *kHIDRemoteTransport = @"Transport"; + +// Distributed notifications +NSString *kHIDRemoteDNHIDRemotePing = @"com.candelair.ping"; +NSString *kHIDRemoteDNHIDRemoteRetry = @"com.candelair.retry"; +NSString *kHIDRemoteDNHIDRemoteStatus = @"com.candelair.status"; + +// Distributed notifications userInfo keys and values +NSString *kHIDRemoteDNStatusHIDRemoteVersionKey = @"HIDRemoteVersion"; +NSString *kHIDRemoteDNStatusPIDKey = @"PID"; +NSString *kHIDRemoteDNStatusModeKey = @"Mode"; +NSString *kHIDRemoteDNStatusUnusedButtonCodesKey = @"UnusedButtonCodes"; +NSString *kHIDRemoteDNStatusActionKey = @"Action"; +NSString *kHIDRemoteDNStatusRemoteControlCountKey = @"RemoteControlCount"; +NSString *kHIDRemoteDNStatusReturnToPIDKey = @"ReturnToPID"; +NSString *kHIDRemoteDNStatusActionStart = @"start"; +NSString *kHIDRemoteDNStatusActionStop = @"stop"; +NSString *kHIDRemoteDNStatusActionUpdate = @"update"; diff --git a/tools/EventClients/Clients/OSXRemote/RemoteControlWrapper/AppleRemote.h b/tools/EventClients/Clients/OSXRemote/RemoteControlWrapper/AppleRemote.h deleted file mode 100644 index 4bb2f0998d..0000000000 --- a/tools/EventClients/Clients/OSXRemote/RemoteControlWrapper/AppleRemote.h +++ /dev/null @@ -1,44 +0,0 @@ -/***************************************************************************** - * RemoteControlWrapper.h - * RemoteControlWrapper - * - * Created by Martin Kahr on 11.03.06 under a MIT-style license. - * Copyright (c) 2006 martinkahr.com. All rights reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED ‚ÄúAS IS‚Äù, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - *****************************************************************************/ - -#import <Cocoa/Cocoa.h> -#import "HIDRemoteControlDevice.h" - -/* Interacts with the Apple Remote Control HID device - The class is not thread safe - */ -@interface AppleRemote : HIDRemoteControlDevice { - int deviceID; -} - -//AppleRemote supports different device IDs (as long they are not paired) -//the deviceID of last sent button can be queried with - (int) deviceID -- (int) deviceID; - -//overwritten from baseclass to get notified of device changes -- (eCookieModifier) handleCookie: (long)f_cookie value:(int) f_value; -@end diff --git a/tools/EventClients/Clients/OSXRemote/RemoteControlWrapper/AppleRemote.m b/tools/EventClients/Clients/OSXRemote/RemoteControlWrapper/AppleRemote.m deleted file mode 100644 index 152e755aa5..0000000000 --- a/tools/EventClients/Clients/OSXRemote/RemoteControlWrapper/AppleRemote.m +++ /dev/null @@ -1,108 +0,0 @@ -/***************************************************************************** - * RemoteControlWrapper.m - * RemoteControlWrapper - * - * Created by Martin Kahr on 11.03.06 under a MIT-style license. - * Copyright (c) 2006 martinkahr.com. All rights reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - *****************************************************************************/ - -#import "AppleRemote.h" - -#import <mach/mach.h> -#import <mach/mach_error.h> -#import <IOKit/IOKitLib.h> -#import <IOKit/IOCFPlugIn.h> -#import <IOKit/hid/IOHIDKeys.h> - -const char* AppleRemoteDeviceName = "AppleIRController"; - -// the WWDC 07 Leopard Build is missing the constant -#ifndef NSAppKitVersionNumber10_4 -#define NSAppKitVersionNumber10_4 824 -#endif - -@implementation AppleRemote - -+ (const char*) remoteControlDeviceName { - return AppleRemoteDeviceName; -} - -- (void) setCookieMappingInDictionary: (NSMutableDictionary*) _cookieToButtonMapping { - if (floor(NSAppKitVersionNumber) <= NSAppKitVersionNumber10_4) { - // 10.4.x Tiger - [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonPlus] forKey:@"14_12_11_6_"]; - [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonMinus] forKey:@"14_13_11_6_"]; - [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonMenu] forKey:@"14_7_6_14_7_6_"]; - [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonPlay] forKey:@"14_8_6_14_8_6_"]; - [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonRight] forKey:@"14_9_6_14_9_6_"]; - [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonLeft] forKey:@"14_10_6_14_10_6_"]; - [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonRight_Hold] forKey:@"14_6_4_2_"]; - [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonLeft_Hold] forKey:@"14_6_3_2_"]; - [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonMenu_Hold] forKey:@"14_6_14_6_"]; - [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonPlay_Hold] forKey:@"18_14_6_18_14_6_"]; - [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteControl_Switched] forKey:@"19_"]; - } else { - // 10.5.x Leopard - [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonPlus] forKey:@"31_29_28_19_18_"]; - [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonMinus] forKey:@"31_30_28_19_18_"]; - [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonMenu] forKey:@"31_20_19_18_31_20_19_18_"]; - [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonPlay] forKey:@"31_21_19_18_31_21_19_18_"]; - [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonRight] forKey:@"31_22_19_18_31_22_19_18_"]; - [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonLeft] forKey:@"31_23_19_18_31_23_19_18_"]; - [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonRight_Hold] forKey:@"31_19_18_4_2_"]; - [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonLeft_Hold] forKey:@"31_19_18_3_2_"]; - [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonMenu_Hold] forKey:@"31_19_18_31_19_18_"]; - [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonPlay_Hold] forKey:@"35_31_19_18_35_31_19_18_"]; - [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteControl_Switched] forKey:@"19_"]; - [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteControl_Switched] forKey:@"39_"]; - } -} - -- (void) sendRemoteButtonEvent: (RemoteControlEventIdentifier) event pressedDown: (BOOL) pressedDown { - if (pressedDown == NO && event == kRemoteButtonMenu_Hold) { - // There is no seperate event for pressed down on menu hold. We are simulating that event here - [super sendRemoteButtonEvent:event pressedDown:YES]; - } - - [super sendRemoteButtonEvent:event pressedDown:pressedDown]; - - if (pressedDown && (event == kRemoteButtonRight || event == kRemoteButtonLeft || event == kRemoteButtonPlay || event == kRemoteButtonMenu || event == kRemoteButtonPlay_Hold)) { - // There is no seperate event when the button is being released. We are simulating that event here - [super sendRemoteButtonEvent:event pressedDown:NO]; - } -} - -- (eCookieModifier) handleCookie: (long)f_cookie value:(int) f_value { - switch(f_cookie) - { - case 39: - deviceID = f_value; - return PASS_COOKIE; - default: - return [super handleCookie:f_cookie value:f_value]; - } -} - -- (int) deviceID { - return deviceID; -} -@end diff --git a/tools/EventClients/Clients/OSXRemote/RemoteControlWrapper/GlobalKeyboardDevice.h b/tools/EventClients/Clients/OSXRemote/RemoteControlWrapper/GlobalKeyboardDevice.h deleted file mode 100644 index bcb11b4245..0000000000 --- a/tools/EventClients/Clients/OSXRemote/RemoteControlWrapper/GlobalKeyboardDevice.h +++ /dev/null @@ -1,49 +0,0 @@ -/***************************************************************************** - * GlobalKeyboardDevice.h - * RemoteControlWrapper - * - * Created by Martin Kahr on 11.03.06 under a MIT-style license. - * Copyright (c) 2006 martinkahr.com. All rights reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - *****************************************************************************/ - -#import <Cocoa/Cocoa.h> -#import <Carbon/Carbon.h> - -#import "RemoteControl.h" - -/* - This class registers for a number of global keyboard shortcuts to simulate a remote control - */ -@interface GlobalKeyboardDevice : RemoteControl { - - NSMutableDictionary* hotKeyRemoteEventMapping; - EventHandlerRef eventHandlerRef; - -} - -- (void) mapRemoteButton: (RemoteControlEventIdentifier) remoteButtonIdentifier defaultKeycode: (unsigned int) defaultKeycode defaultModifiers: (unsigned int) defaultModifiers; - -- (BOOL)registerHotKeyCode: (unsigned int) keycode modifiers: (unsigned int) modifiers remoteEventIdentifier: (RemoteControlEventIdentifier) identifier; - -static OSStatus hotKeyEventHandler(EventHandlerCallRef inHandlerRef, EventRef inEvent, void* refCon ); - -@end diff --git a/tools/EventClients/Clients/OSXRemote/RemoteControlWrapper/GlobalKeyboardDevice.m b/tools/EventClients/Clients/OSXRemote/RemoteControlWrapper/GlobalKeyboardDevice.m deleted file mode 100644 index f568d1f1a3..0000000000 --- a/tools/EventClients/Clients/OSXRemote/RemoteControlWrapper/GlobalKeyboardDevice.m +++ /dev/null @@ -1,241 +0,0 @@ -/***************************************************************************** - * GlobalKeyboardDevice.m - * RemoteControlWrapper - * - * Created by Martin Kahr on 11.03.06 under a MIT-style license. - * Copyright (c) 2006 martinkahr.com. All rights reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - *****************************************************************************/ - - -#import "GlobalKeyboardDevice.h" - -#define F1 122 -#define F2 120 -#define F3 99 -#define F4 118 -#define F5 96 -#define F6 97 -#define F7 98 - -/* - the following default keys are read and shall be used to change the keyboard mapping - - mac.remotecontrols.GlobalKeyboardDevice.plus_modifiers - mac.remotecontrols.GlobalKeyboardDevice.plus_keycode - mac.remotecontrols.GlobalKeyboardDevice.minus_modifiers - mac.remotecontrols.GlobalKeyboardDevice.minus_keycode - mac.remotecontrols.GlobalKeyboardDevice.play_modifiers - mac.remotecontrols.GlobalKeyboardDevice.play_keycode - mac.remotecontrols.GlobalKeyboardDevice.left_modifiers - mac.remotecontrols.GlobalKeyboardDevice.left_keycode - mac.remotecontrols.GlobalKeyboardDevice.right_modifiers - mac.remotecontrols.GlobalKeyboardDevice.right_keycode - mac.remotecontrols.GlobalKeyboardDevice.menu_modifiers - mac.remotecontrols.GlobalKeyboardDevice.menu_keycode - mac.remotecontrols.GlobalKeyboardDevice.playhold_modifiers - mac.remotecontrols.GlobalKeyboardDevice.playhold_keycode - */ - - -@implementation GlobalKeyboardDevice - -- (id) initWithDelegate: (id) _remoteControlDelegate { - if (self = [super initWithDelegate: _remoteControlDelegate]) { - hotKeyRemoteEventMapping = [[NSMutableDictionary alloc] init]; - - unsigned int modifiers = cmdKey + shiftKey /*+ optionKey*/ + controlKey; - - [self mapRemoteButton:kRemoteButtonPlus defaultKeycode:F1 defaultModifiers:modifiers]; - [self mapRemoteButton:kRemoteButtonMinus defaultKeycode:F2 defaultModifiers:modifiers]; - [self mapRemoteButton:kRemoteButtonPlay defaultKeycode:F3 defaultModifiers:modifiers]; - [self mapRemoteButton:kRemoteButtonLeft defaultKeycode:F4 defaultModifiers:modifiers]; - [self mapRemoteButton:kRemoteButtonRight defaultKeycode:F5 defaultModifiers:modifiers]; - [self mapRemoteButton:kRemoteButtonMenu defaultKeycode:F6 defaultModifiers:modifiers]; - [self mapRemoteButton:kRemoteButtonPlay_Hold defaultKeycode:F7 defaultModifiers:modifiers]; - } - return self; -} - -- (void) dealloc { - [hotKeyRemoteEventMapping release]; - [super dealloc]; -} - -- (void) mapRemoteButton: (RemoteControlEventIdentifier) remoteButtonIdentifier defaultKeycode: (unsigned int) defaultKeycode defaultModifiers: (unsigned int) defaultModifiers { - NSString* defaultsKey; - - switch(remoteButtonIdentifier) { - case kRemoteButtonPlus: - defaultsKey = @"plus"; - break; - case kRemoteButtonMinus: - defaultsKey = @"minus"; - break; - case kRemoteButtonMenu: - defaultsKey = @"menu"; - break; - case kRemoteButtonPlay: - defaultsKey = @"play"; - break; - case kRemoteButtonRight: - defaultsKey = @"right"; - break; - case kRemoteButtonLeft: - defaultsKey = @"left"; - break; - case kRemoteButtonPlay_Hold: - defaultsKey = @"playhold"; - break; - default: - NSLog(@"Unknown global keyboard defaults key for remote button identifier %d", remoteButtonIdentifier); - } - - NSNumber* modifiersCfg = [[NSUserDefaults standardUserDefaults] objectForKey: [NSString stringWithFormat: @"mac.remotecontrols.GlobalKeyboardDevice.%@_modifiers", defaultsKey]]; - NSNumber* keycodeCfg = [[NSUserDefaults standardUserDefaults] objectForKey: [NSString stringWithFormat: @"mac.remotecontrols.GlobalKeyboardDevice.%@_keycode", defaultsKey]]; - - unsigned int modifiers = defaultModifiers; - if (modifiersCfg) modifiers = [modifiersCfg unsignedIntValue]; - - unsigned int keycode = defaultKeycode; - if (keycodeCfg) keycode = [keycodeCfg unsignedIntValue]; - - [self registerHotKeyCode: keycode modifiers: modifiers remoteEventIdentifier: remoteButtonIdentifier]; -} - -- (void) setListeningToRemote: (BOOL) value { - if (value == [self isListeningToRemote]) return; - if (value) { - [self startListening: self]; - } else { - [self stopListening: self]; - } -} -- (BOOL) isListeningToRemote { - return (eventHandlerRef!=NULL); -} - -- (IBAction) startListening: (id) sender { - - if (eventHandlerRef) return; - - EventTypeSpec eventSpec[2] = { - { kEventClassKeyboard, kEventHotKeyPressed }, - { kEventClassKeyboard, kEventHotKeyReleased } - }; - - InstallEventHandler( GetEventDispatcherTarget(), - (EventHandlerProcPtr)hotKeyEventHandler, - 2, eventSpec, self, &eventHandlerRef); -} -- (IBAction) stopListening: (id) sender { - RemoveEventHandler(eventHandlerRef); - eventHandlerRef = NULL; -} - -- (BOOL) sendsEventForButtonIdentifier: (RemoteControlEventIdentifier) identifier { - NSEnumerator* values = [hotKeyRemoteEventMapping objectEnumerator]; - NSNumber* remoteIdentifier; - while( (remoteIdentifier = [values nextObject]) ) { - if ([remoteIdentifier unsignedIntValue] == identifier) return YES; - } - return NO; -} - -+ (const char*) remoteControlDeviceName { - return "Keyboard"; -} - -- (BOOL)registerHotKeyCode: (unsigned int) keycode modifiers: (unsigned int) modifiers remoteEventIdentifier: (RemoteControlEventIdentifier) identifier { - OSStatus err; - EventHotKeyID hotKeyID; - EventHotKeyRef carbonHotKey; - - hotKeyID.signature = 'PTHk'; - hotKeyID.id = (long)keycode; - - err = RegisterEventHotKey(keycode, modifiers, hotKeyID, GetEventDispatcherTarget(), (int)nil, &carbonHotKey ); - - if( err ) - return NO; - - [hotKeyRemoteEventMapping setObject: [NSNumber numberWithInt:identifier] forKey: [NSNumber numberWithUnsignedInt: hotKeyID.id]]; - - return YES; -} -/* - - (void)unregisterHotKey: (PTHotKey*)hotKey - { - OSStatus err; - EventHotKeyRef carbonHotKey; - NSValue* key; - - if( [[self allHotKeys] containsObject: hotKey] == NO ) - return; - - carbonHotKey = [self _carbonHotKeyForHotKey: hotKey]; - NSAssert( carbonHotKey != nil, @"" ); - - err = UnregisterEventHotKey( carbonHotKey ); - //Watch as we ignore 'err': - - key = [NSValue valueWithPointer: carbonHotKey]; - [mHotKeys removeObjectForKey: key]; - - [self _updateEventHandler]; - - //See that? Completely ignored - } - */ - -- (RemoteControlEventIdentifier) remoteControlEventIdentifierForID: (unsigned int) id { - NSNumber* remoteEventIdentifier = [hotKeyRemoteEventMapping objectForKey:[NSNumber numberWithUnsignedInt: id]]; - return [remoteEventIdentifier unsignedIntValue]; -} - -- (void) sendRemoteButtonEvent: (RemoteControlEventIdentifier) event pressedDown: (BOOL) pressedDown { - [delegate sendRemoteButtonEvent: event pressedDown: pressedDown remoteControl:self]; -} - -static RemoteControlEventIdentifier lastEvent; - -static OSStatus hotKeyEventHandler(EventHandlerCallRef inHandlerRef, EventRef inEvent, void* userData ) -{ - GlobalKeyboardDevice* keyboardDevice = (GlobalKeyboardDevice*) userData; - EventHotKeyID hkCom; - GetEventParameter(inEvent,kEventParamDirectObject,typeEventHotKeyID,NULL,sizeof(hkCom),NULL,&hkCom); - - RemoteControlEventIdentifier identifier = [keyboardDevice remoteControlEventIdentifierForID:hkCom.id]; - if (identifier == 0) return noErr; - - BOOL pressedDown = YES; - if (identifier != lastEvent) { - lastEvent = identifier; - } else { - lastEvent = 0; - pressedDown = NO; - } - [keyboardDevice sendRemoteButtonEvent: identifier pressedDown: pressedDown]; - - return noErr; -} - -@end diff --git a/tools/EventClients/Clients/OSXRemote/RemoteControlWrapper/HIDRemoteControlDevice.h b/tools/EventClients/Clients/OSXRemote/RemoteControlWrapper/HIDRemoteControlDevice.h deleted file mode 100644 index 1bd3dff60a..0000000000 --- a/tools/EventClients/Clients/OSXRemote/RemoteControlWrapper/HIDRemoteControlDevice.h +++ /dev/null @@ -1,77 +0,0 @@ -/***************************************************************************** - * HIDRemoteControlDevice.h - * RemoteControlWrapper - * - * Created by Martin Kahr on 11.03.06 under a MIT-style license. - * Copyright (c) 2006 martinkahr.com. All rights reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED ‚ÄúAS IS‚Äù, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - *****************************************************************************/ - -#import <Cocoa/Cocoa.h> -#import <Carbon/Carbon.h> -#import <IOKit/hid/IOHIDLib.h> - -#import "RemoteControl.h" - -typedef enum _eCookieModifier{ - DISCARD_COOKIE = 0, - PASS_COOKIE -} eCookieModifier; - -/* - Base class for HID based remote control devices - */ -@interface HIDRemoteControlDevice : RemoteControl { - IOHIDDeviceInterface** hidDeviceInterface; - IOHIDQueueInterface** queue; - NSMutableArray* allCookies; - NSMutableDictionary* cookieToButtonMapping; - CFRunLoopSourceRef eventSource; - - BOOL fixSecureEventInputBug; - BOOL openInExclusiveMode; - BOOL processesBacklog; - - int supportedButtonEvents; - - EventHandlerUPP appSwitchedHandlerUPP; - EventHandlerRef appSwitchedHandlerRef; -} - -// When your application needs to much time on the main thread when processing an event other events -// may already be received which are put on a backlog. As soon as your main thread -// has some spare time this backlog is processed and may flood your delegate with calls. -// Backlog processing is turned off by default. -- (BOOL) processesBacklog; -- (void) setProcessesBacklog: (BOOL) value; - -// methods that should be overwritten by subclasses -- (void) setCookieMappingInDictionary: (NSMutableDictionary*) cookieToButtonMapping; - -- (void) sendRemoteButtonEvent: (RemoteControlEventIdentifier) event pressedDown: (BOOL) pressedDown; - -//overwrite this to be able to interact with cookie handling -//by default (f_cookie == 5) is discarded -- (eCookieModifier) handleCookie: (long)f_cookie value:(int) f_value; - -+ (BOOL) isRemoteAvailable; - -@end diff --git a/tools/EventClients/Clients/OSXRemote/RemoteControlWrapper/HIDRemoteControlDevice.m b/tools/EventClients/Clients/OSXRemote/RemoteControlWrapper/HIDRemoteControlDevice.m deleted file mode 100644 index 5fa61c46b5..0000000000 --- a/tools/EventClients/Clients/OSXRemote/RemoteControlWrapper/HIDRemoteControlDevice.m +++ /dev/null @@ -1,547 +0,0 @@ -/***************************************************************************** - * HIDRemoteControlDevice.m - * RemoteControlWrapper - * - * Created by Martin Kahr on 11.03.06 under a MIT-style license. - * Copyright (c) 2006 martinkahr.com. All rights reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - *****************************************************************************/ - -#import "HIDRemoteControlDevice.h" - -#import <mach/mach.h> -#import <mach/mach_error.h> -#import <IOKit/IOKitLib.h> -#import <IOKit/IOCFPlugIn.h> -#import <IOKit/hid/IOHIDKeys.h> -#import "XBMCDebugHelpers.h" - -@interface HIDRemoteControlDevice (PrivateMethods) -- (NSDictionary*) cookieToButtonMapping; -- (IOHIDQueueInterface**) queue; -- (IOHIDDeviceInterface**) hidDeviceInterface; -- (void) removeNotifcationObserver; -- (void) remoteControlAvailable:(NSNotification *)notification; -- (void) enableSecureInputFix; -- (void) disableSecureInputFix; -@end - -@interface HIDRemoteControlDevice (IOKitMethods) -+ (io_object_t) findRemoteDevice; -- (IOHIDDeviceInterface**) createInterfaceForDevice: (io_object_t) hidDevice; -- (BOOL) initializeCookies; -- (BOOL) openDevice; -@end - -//handler to regrab device if openend in exclusive mode on app-switches -//installed in startListening and removed in stopListening -pascal OSStatus appSwitchedEventHandler(EventHandlerCallRef nextHandler, - EventRef switchEvent, - void* userData); - -@implementation HIDRemoteControlDevice - -+ (const char*) remoteControlDeviceName { - return ""; -} - -+ (BOOL) isRemoteAvailable { - io_object_t hidDevice = [self findRemoteDevice]; - if (hidDevice != 0) { - IOObjectRelease(hidDevice); - return YES; - } else { - return NO; - } -} - -- (id) initWithDelegate: (id) _remoteControlDelegate { - if ([[self class] isRemoteAvailable] == NO) return nil; - - if ( self = [super initWithDelegate: _remoteControlDelegate] ) { - openInExclusiveMode = YES; - queue = NULL; - hidDeviceInterface = NULL; - cookieToButtonMapping = [[NSMutableDictionary alloc] init]; - - [self setCookieMappingInDictionary: cookieToButtonMapping]; - - NSEnumerator* enumerator = [cookieToButtonMapping objectEnumerator]; - NSNumber* identifier; - supportedButtonEvents = 0; - while(identifier = [enumerator nextObject]) { - supportedButtonEvents |= [identifier intValue]; - } - - fixSecureEventInputBug = [[NSUserDefaults standardUserDefaults] boolForKey: @"remoteControlWrapperFixSecureEventInputBug"]; - appSwitchedHandlerUPP = NewEventHandlerUPP(appSwitchedEventHandler); - } - - return self; -} - -- (void) dealloc { - [self removeNotifcationObserver]; - [self stopListening:self]; - [cookieToButtonMapping release]; - [super dealloc]; -} - -- (void) sendRemoteButtonEvent: (RemoteControlEventIdentifier) event pressedDown: (BOOL) pressedDown { - [delegate sendRemoteButtonEvent: event pressedDown: pressedDown remoteControl:self]; -} - -- (void) setCookieMappingInDictionary: (NSMutableDictionary*) cookieToButtonMapping { -} -- (int) remoteIdSwitchCookie { - return 0; -} - -- (BOOL) sendsEventForButtonIdentifier: (RemoteControlEventIdentifier) identifier { - return (supportedButtonEvents & identifier) == identifier; -} - -- (BOOL) isListeningToRemote { - return (hidDeviceInterface != NULL && allCookies != NULL && queue != NULL); -} - -- (void) setListeningToRemote: (BOOL) value { - if (value == NO) { - [self stopListening:self]; - } else { - [self startListening:self]; - } -} - -- (BOOL) isOpenInExclusiveMode { - return openInExclusiveMode; -} -- (void) setOpenInExclusiveMode: (BOOL) value { - openInExclusiveMode = value; -} - -- (BOOL) processesBacklog { - return processesBacklog; -} -- (void) setProcessesBacklog: (BOOL) value { - processesBacklog = value; -} - -//handler to regrab device if openend in exclusive mode on app-switches -//installed in startListening and removed in stopListening -pascal OSStatus appSwitchedEventHandler(EventHandlerCallRef nextHandler, - EventRef switchEvent, - void* userData) -{ - HIDRemoteControlDevice* p_this = (HIDRemoteControlDevice*)userData; - if (p_this->hidDeviceInterface) - { - if ((*(p_this->hidDeviceInterface))->close(p_this->hidDeviceInterface) != KERN_SUCCESS) - ELOG("failed closing Apple Remote device\n"); - - if ((*(p_this->hidDeviceInterface))->open(p_this->hidDeviceInterface, kIOHIDOptionsTypeSeizeDevice) != KERN_SUCCESS) - ELOG("failed opening Apple Remote device\n"); - } - //DLOG("Reopened Apple Remote in exclusive mode\n"); - return 0; -} - -- (IBAction) startListening: (id) sender { - if ([self isListeningToRemote]) return; - - // 4th July 2007 - // - // A security update in february of 2007 introduced an odd behavior. - // Whenever SecureEventInput is activated or deactivated the exclusive access - // to the remote control device is lost. This leads to very strange behavior where - // a press on the Menu button activates FrontRow while your app still gets the event. - // A great number of people have complained about this. - // - // Enabling the SecureEventInput and keeping it enabled does the trick. - // - // I'm pretty sure this is a kind of bug at Apple and I'm in contact with the responsible - // Apple Engineer. This solution is not a perfect one - I know. - // One of the side effects is that applications that listen for special global keyboard shortcuts (like Quicksilver) - // may get into problems as they no longer get the events. - // As there is no official Apple Remote API from Apple I also failed to open a technical incident on this. - // - // Note that there is a corresponding DisableSecureEventInput in the stopListening method below. - // - [self enableSecureInputFix]; - - [self removeNotifcationObserver]; - - io_object_t hidDevice = [[self class] findRemoteDevice]; - if (hidDevice == 0) return; - - if ([self createInterfaceForDevice:hidDevice] == NULL) { - goto error; - } - - if ([self initializeCookies]==NO) { - goto error; - } - - if ([self openDevice]==NO) { - goto error; - } - // be KVO friendly - [self willChangeValueForKey:@"listeningToRemote"]; - [self didChangeValueForKey:@"listeningToRemote"]; - goto cleanup; - -error: - [self stopListening:self]; - [self disableSecureInputFix]; - -cleanup: - IOObjectRelease(hidDevice); -} - -- (IBAction) stopListening: (id) sender { - if ([self isListeningToRemote]==NO) return; - - BOOL sendNotification = NO; - - if (eventSource != NULL) { - CFRunLoopRemoveSource(CFRunLoopGetCurrent(), eventSource, kCFRunLoopDefaultMode); - CFRelease(eventSource); - eventSource = NULL; - } - if (queue != NULL) { - (*queue)->stop(queue); - - //dispose of queue - (*queue)->dispose(queue); - - //release the queue we allocated - (*queue)->Release(queue); - - queue = NULL; - - sendNotification = YES; - } - - if (allCookies != nil) { - [allCookies autorelease]; - allCookies = nil; - } - - if (hidDeviceInterface != NULL) { - //close the device - (*hidDeviceInterface)->close(hidDeviceInterface); - - //release the interface - (*hidDeviceInterface)->Release(hidDeviceInterface); - - hidDeviceInterface = NULL; - } - [self disableSecureInputFix]; - - if ([self isOpenInExclusiveMode] && sendNotification) { - [[self class] sendFinishedNotifcationForAppIdentifier: nil]; - } - // be KVO friendly - [self willChangeValueForKey:@"listeningToRemote"]; - [self didChangeValueForKey:@"listeningToRemote"]; -} - -- (eCookieModifier) handleCookie: (long)f_cookie value:(int) f_value { - if(f_cookie == 5) - return DISCARD_COOKIE; - else - return PASS_COOKIE; -} - -@end - -@implementation HIDRemoteControlDevice (PrivateMethods) - -- (IOHIDQueueInterface**) queue { - return queue; -} - -- (IOHIDDeviceInterface**) hidDeviceInterface { - return hidDeviceInterface; -} - - -- (NSDictionary*) cookieToButtonMapping { - return cookieToButtonMapping; -} - -- (void) removeNotifcationObserver { - [[NSDistributedNotificationCenter defaultCenter] removeObserver:self name:FINISHED_USING_REMOTE_CONTROL_NOTIFICATION object:nil]; -} - -- (void) remoteControlAvailable:(NSNotification *)notification { - [self removeNotifcationObserver]; - [self startListening: self]; -} - -- (void) enableSecureInputFix { - if ([self isOpenInExclusiveMode]) { - //2 ways to fix exclusive mode, either the one mentioned above - if (fixSecureEventInputBug) - EnableSecureEventInput(); - else { - //or registering for all app-switch events and just reopening the device on each switch - const EventTypeSpec applicationEvents[] = { { kEventClassApplication, kEventAppFrontSwitched } }; - InstallApplicationEventHandler(appSwitchedHandlerUPP, - GetEventTypeCount(applicationEvents), applicationEvents, self, &appSwitchedHandlerRef); - } - } -} - -- (void) disableSecureInputFix{ - if ([self isOpenInExclusiveMode]){ - if(fixSecureEventInputBug) - DisableSecureEventInput(); - else{ - RemoveEventHandler(appSwitchedHandlerRef); - } - } -} - -@end - -/* Callback method for the device queue - Will be called for any event of any type (cookie) to which we subscribe - */ -static void QueueCallbackFunction(void* target, IOReturn result, void* refcon, void* sender) { - if (target < 0) { - NSLog(@"QueueCallbackFunction called with invalid target!"); - return; - } - NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; - - HIDRemoteControlDevice* remote = (HIDRemoteControlDevice*)target; - IOHIDEventStruct event; - AbsoluteTime zeroTime = {0,0}; - NSMutableString* cookieString = [NSMutableString stringWithCapacity: 10]; - SInt32 sumOfValues = 0; - NSNumber* lastSubButtonId = nil; - while (result == kIOReturnSuccess) - { - result = (*[remote queue])->getNextEvent([remote queue], &event, zeroTime, 0); - if ( result != kIOReturnSuccess ) - continue; - - //printf("%d %d %d\n", event.elementCookie, event.value, event.longValue); - if([remote handleCookie:(long)event.elementCookie value:event.value] != DISCARD_COOKIE){ - sumOfValues+=event.value; - [cookieString appendString:[NSString stringWithFormat:@"%d_", event.elementCookie]]; - } - //check if this is a valid button - NSNumber* buttonId = [[remote cookieToButtonMapping] objectForKey: cookieString]; - if (buttonId != nil){ - if([remote processesBacklog]) { - //send the button - [remote sendRemoteButtonEvent: [buttonId intValue] pressedDown: (sumOfValues>0)]; - //reset - } else { - //store button for later use - lastSubButtonId = buttonId; - } - sumOfValues = 0; - cookieString = [NSMutableString stringWithCapacity: 10]; - } - } - if ([remote processesBacklog] == NO && lastSubButtonId != nil) { - // process the last event of the backlog and assume that the button is not pressed down any longer. - // The events in the backlog do not seem to be in order and therefore (in rare cases) the last event might be - // a button pressed down event while in reality the user has released it. - // NSLog(@"processing last event of backlog"); - [remote sendRemoteButtonEvent: [lastSubButtonId intValue] pressedDown: (sumOfValues>0)]; - } - if ([cookieString length] > 0) { - NSLog(@"Unknown button for cookiestring %@", cookieString); - } - [pool release]; -} - -@implementation HIDRemoteControlDevice (IOKitMethods) - -- (IOHIDDeviceInterface**) createInterfaceForDevice: (io_object_t) hidDevice { - io_name_t className; - IOCFPlugInInterface** plugInInterface = NULL; - HRESULT plugInResult = S_OK; - SInt32 score = 0; - IOReturn ioReturnValue = kIOReturnSuccess; - - hidDeviceInterface = NULL; - - ioReturnValue = IOObjectGetClass(hidDevice, className); - - if (ioReturnValue != kIOReturnSuccess) { - NSLog(@"Error: Failed to get class name."); - return NULL; - } - - ioReturnValue = IOCreatePlugInInterfaceForService(hidDevice, - kIOHIDDeviceUserClientTypeID, - kIOCFPlugInInterfaceID, - &plugInInterface, - &score); - if (ioReturnValue == kIOReturnSuccess) - { - //Call a method of the intermediate plug-in to create the device interface - plugInResult = (*plugInInterface)->QueryInterface(plugInInterface, CFUUIDGetUUIDBytes(kIOHIDDeviceInterfaceID), (LPVOID) &hidDeviceInterface); - - if (plugInResult != S_OK) { - NSLog(@"Error: Couldn't create HID class device interface"); - } - // Release - if (plugInInterface) (*plugInInterface)->Release(plugInInterface); - } - return hidDeviceInterface; -} - -- (BOOL) initializeCookies { - IOHIDDeviceInterface122** handle = (IOHIDDeviceInterface122**)hidDeviceInterface; - IOHIDElementCookie cookie; - long usage; - long usagePage; - id object; - NSArray* elements = nil; - NSDictionary* element; - IOReturn success; - - if (!handle || !(*handle)) return NO; - - // Copy all elements, since we're grabbing most of the elements - // for this device anyway, and thus, it's faster to iterate them - // ourselves. When grabbing only one or two elements, a matching - // dictionary should be passed in here instead of NULL. - success = (*handle)->copyMatchingElements(handle, NULL, (CFArrayRef*)&elements); - - if (success == kIOReturnSuccess) { - - [elements autorelease]; - /* - cookies = calloc(NUMBER_OF_APPLE_REMOTE_ACTIONS, sizeof(IOHIDElementCookie)); - memset(cookies, 0, sizeof(IOHIDElementCookie) * NUMBER_OF_APPLE_REMOTE_ACTIONS); - */ - allCookies = [[NSMutableArray alloc] init]; - - NSEnumerator *elementsEnumerator = [elements objectEnumerator]; - - while (element = [elementsEnumerator nextObject]) { - //Get cookie - object = [element valueForKey: (NSString*)CFSTR(kIOHIDElementCookieKey) ]; - if (object == nil || ![object isKindOfClass:[NSNumber class]]) continue; - if (object == 0 || CFGetTypeID(object) != CFNumberGetTypeID()) continue; - cookie = (IOHIDElementCookie) [object longValue]; - - //Get usage - object = [element valueForKey: (NSString*)CFSTR(kIOHIDElementUsageKey) ]; - if (object == nil || ![object isKindOfClass:[NSNumber class]]) continue; - usage = [object longValue]; - - //Get usage page - object = [element valueForKey: (NSString*)CFSTR(kIOHIDElementUsagePageKey) ]; - if (object == nil || ![object isKindOfClass:[NSNumber class]]) continue; - usagePage = [object longValue]; - - [allCookies addObject: [NSNumber numberWithInt:(int)cookie]]; - } - } else { - return NO; - } - - return YES; -} - -- (BOOL) openDevice { - HRESULT result; - - IOHIDOptionsType openMode = kIOHIDOptionsTypeNone; - if ([self isOpenInExclusiveMode]) openMode = kIOHIDOptionsTypeSeizeDevice; - IOReturn ioReturnValue = (*hidDeviceInterface)->open(hidDeviceInterface, openMode); - - if (ioReturnValue == KERN_SUCCESS) { - queue = (*hidDeviceInterface)->allocQueue(hidDeviceInterface); - if (queue) { - result = (*queue)->create(queue, 0, 12); //depth: maximum number of elements in queue before oldest elements in queue begin to be lost. - - IOHIDElementCookie cookie; - NSEnumerator *allCookiesEnumerator = [allCookies objectEnumerator]; - - while (cookie = (IOHIDElementCookie)[[allCookiesEnumerator nextObject] intValue]) { - (*queue)->addElement(queue, cookie, 0); - } - - // add callback for async events - ioReturnValue = (*queue)->createAsyncEventSource(queue, &eventSource); - if (ioReturnValue == KERN_SUCCESS) { - ioReturnValue = (*queue)->setEventCallout(queue,QueueCallbackFunction, self, NULL); - if (ioReturnValue == KERN_SUCCESS) { - CFRunLoopAddSource(CFRunLoopGetCurrent(), eventSource, kCFRunLoopDefaultMode); - - //start data delivery to queue - (*queue)->start(queue); - return YES; - } else { - NSLog(@"Error when setting event callback"); - } - } else { - NSLog(@"Error when creating async event source"); - } - } else { - NSLog(@"Error when opening device"); - } - } else if (ioReturnValue == kIOReturnExclusiveAccess) { - // the device is used exclusive by another application - - // 1. we register for the FINISHED_USING_REMOTE_CONTROL_NOTIFICATION notification - [[NSDistributedNotificationCenter defaultCenter] addObserver:self selector:@selector(remoteControlAvailable:) name:FINISHED_USING_REMOTE_CONTROL_NOTIFICATION object:nil]; - - // 2. send a distributed notification that we wanted to use the remote control - [[self class] sendRequestForRemoteControlNotification]; - } - return NO; -} - -+ (io_object_t) findRemoteDevice { - CFMutableDictionaryRef hidMatchDictionary = NULL; - IOReturn ioReturnValue = kIOReturnSuccess; - io_iterator_t hidObjectIterator = 0; - io_object_t hidDevice = 0; - - // Set up a matching dictionary to search the I/O Registry by class - // name for all HID class devices - hidMatchDictionary = IOServiceMatching([self remoteControlDeviceName]); - - // Now search I/O Registry for matching devices. - ioReturnValue = IOServiceGetMatchingServices(kIOMasterPortDefault, hidMatchDictionary, &hidObjectIterator); - - if ((ioReturnValue == kIOReturnSuccess) && (hidObjectIterator != 0)) { - hidDevice = IOIteratorNext(hidObjectIterator); - } - - // release the iterator - IOObjectRelease(hidObjectIterator); - - return hidDevice; -} - -@end - diff --git a/tools/EventClients/Clients/OSXRemote/RemoteControlWrapper/IRKeyboardEmu.h b/tools/EventClients/Clients/OSXRemote/RemoteControlWrapper/IRKeyboardEmu.h deleted file mode 100644 index 8a842da8f3..0000000000 --- a/tools/EventClients/Clients/OSXRemote/RemoteControlWrapper/IRKeyboardEmu.h +++ /dev/null @@ -1,16 +0,0 @@ -// -// IRKeyboardEmu.h -// XBMCHelper -// -// Created by Stephan Diederich on 14.04.09. -// Copyright 2009 __MyCompanyName__. All rights reserved. -// - -#import <Cocoa/Cocoa.h> -#import "AppleRemote.h" - -@interface IRKeyboardEmu : AppleRemote { - -} - -@end diff --git a/tools/EventClients/Clients/OSXRemote/RemoteControlWrapper/IRKeyboardEmu.m b/tools/EventClients/Clients/OSXRemote/RemoteControlWrapper/IRKeyboardEmu.m deleted file mode 100644 index 5350e16c0c..0000000000 --- a/tools/EventClients/Clients/OSXRemote/RemoteControlWrapper/IRKeyboardEmu.m +++ /dev/null @@ -1,26 +0,0 @@ -// -// IRKeyboardEmu.m -// XBMCHelper -// -// Created by Stephan Diederich on 14.04.09. -// Copyright 2009 __MyCompanyName__. All rights reserved. -// - -#import "IRKeyboardEmu.h" - - -@implementation IRKeyboardEmu - -+ (const char*) remoteControlDeviceName { - return "IRKeyboardEmu"; -} - -- (void) sendRemoteButtonEvent: (RemoteControlEventIdentifier) event pressedDown: (BOOL) pressedDown { - if (pressedDown == NO && event == kRemoteButtonMenu_Hold) { - // There is no seperate event for pressed down on menu hold. We are simulating that event here - [super sendRemoteButtonEvent:event pressedDown:YES]; - } - - [super sendRemoteButtonEvent:event pressedDown:pressedDown]; -} -@end diff --git a/tools/EventClients/Clients/OSXRemote/RemoteControlWrapper/KeyspanFrontRowControl.h b/tools/EventClients/Clients/OSXRemote/RemoteControlWrapper/KeyspanFrontRowControl.h deleted file mode 100644 index 3a8224154f..0000000000 --- a/tools/EventClients/Clients/OSXRemote/RemoteControlWrapper/KeyspanFrontRowControl.h +++ /dev/null @@ -1,39 +0,0 @@ -/***************************************************************************** - * KeyspanFrontRowControl.h - * RemoteControlWrapper - * - * Created by Martin Kahr on 11.03.06 under a MIT-style license. - * Copyright (c) 2006 martinkahr.com. All rights reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - *****************************************************************************/ - - -#import <Cocoa/Cocoa.h> -#import "HIDRemoteControlDevice.h" - -/* Interacts with the Keyspan FrontRow Remote Control HID device - The class is not thread safe - */ -@interface KeyspanFrontRowControl : HIDRemoteControlDevice { - -} - -@end diff --git a/tools/EventClients/Clients/OSXRemote/RemoteControlWrapper/KeyspanFrontRowControl.m b/tools/EventClients/Clients/OSXRemote/RemoteControlWrapper/KeyspanFrontRowControl.m deleted file mode 100644 index 7dbcc853ec..0000000000 --- a/tools/EventClients/Clients/OSXRemote/RemoteControlWrapper/KeyspanFrontRowControl.m +++ /dev/null @@ -1,87 +0,0 @@ -/***************************************************************************** - * KeyspanFrontRowControl.m - * RemoteControlWrapper - * - * Created by Martin Kahr on 11.03.06 under a MIT-style license. - * Copyright (c) 2006 martinkahr.com. All rights reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - *****************************************************************************/ - -#import "KeyspanFrontRowControl.h" -#import <mach/mach.h> -#import <mach/mach_error.h> -#import <IOKit/IOKitLib.h> -#import <IOKit/IOCFPlugIn.h> -#import <IOKit/hid/IOHIDKeys.h> - -@implementation KeyspanFrontRowControl - -- (void) setCookieMappingInDictionary: (NSMutableDictionary*) _cookieToButtonMapping { - [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonPlus] forKey:@"11_18_99_10_"]; - [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonMinus] forKey:@"11_18_98_10_"]; - [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonMenu] forKey:@"11_18_58_10_"]; - [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonPlay] forKey:@"11_18_61_10_"]; - [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonRight] forKey:@"11_18_96_10_"]; - [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonLeft] forKey:@"11_18_97_10_"]; - /* hold events are not being send by this device - [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonRight_Hold] forKey:@"14_6_4_2_"]; - [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonLeft_Hold] forKey:@"14_6_3_2_"]; - [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonMenu_Hold] forKey:@"14_6_14_6_"]; - [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonPlay_Sleep] forKey:@"18_14_6_18_14_6_"]; - [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteControl_Switched] forKey:@"19_"]; - */ -} - -+ (io_object_t) findRemoteDevice { - CFMutableDictionaryRef hidMatchDictionary = NULL; - IOReturn ioReturnValue = kIOReturnSuccess; - io_iterator_t hidObjectIterator = 0; - io_object_t hidDevice = 0; - SInt32 idVendor = 1741; - SInt32 idProduct = 0x420; - - // Set up a matching dictionary to search the I/O Registry by class - // name for all HID class devices - hidMatchDictionary = IOServiceMatching(kIOHIDDeviceKey); - - CFNumberRef numberRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &idVendor); - CFDictionaryAddValue(hidMatchDictionary, CFSTR(kIOHIDVendorIDKey), numberRef); - CFRelease(numberRef); - - numberRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &idProduct); - CFDictionaryAddValue(hidMatchDictionary, CFSTR(kIOHIDProductIDKey), numberRef); - CFRelease(numberRef); - - // Now search I/O Registry for matching devices. - ioReturnValue = IOServiceGetMatchingServices(kIOMasterPortDefault, hidMatchDictionary, &hidObjectIterator); - - if ((ioReturnValue == kIOReturnSuccess) && (hidObjectIterator != 0)) { - hidDevice = IOIteratorNext(hidObjectIterator); - } - - // release the iterator - IOObjectRelease(hidObjectIterator); - - return hidDevice; - -} - -@end diff --git a/tools/EventClients/Clients/OSXRemote/RemoteControlWrapper/MultiClickRemoteBehavior.h b/tools/EventClients/Clients/OSXRemote/RemoteControlWrapper/MultiClickRemoteBehavior.h deleted file mode 100644 index 8b288604f7..0000000000 --- a/tools/EventClients/Clients/OSXRemote/RemoteControlWrapper/MultiClickRemoteBehavior.h +++ /dev/null @@ -1,90 +0,0 @@ -/***************************************************************************** - * MultiClickRemoteBehavior.h - * RemoteControlWrapper - * - * Created by Martin Kahr on 11.03.06 under a MIT-style license. - * Copyright (c) 2006 martinkahr.com. All rights reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - *****************************************************************************/ - - -#import <Cocoa/Cocoa.h> -#import "RemoteControl.h" - -/** - A behavior that adds multiclick and hold events on top of a device. - Events are generated and send to a delegate - */ -@interface MultiClickRemoteBehavior : NSObject { - id delegate; - - // state for simulating plus/minus hold - BOOL simulateHoldEvents; - BOOL lastEventSimulatedHold; - RemoteControlEventIdentifier lastHoldEvent; - NSTimeInterval lastHoldEventTime; - - // state for multi click - unsigned int clickCountEnabledButtons; - NSTimeInterval maxClickTimeDifference; - NSTimeInterval lastClickCountEventTime; - RemoteControlEventIdentifier lastClickCountEvent; - unsigned int eventClickCount; -} - -- (id) init; - -// Delegates are not retained -- (void) setDelegate: (id) delegate; -- (id) delegate; - -// Simulating hold events does deactivate sending of individual requests for pressed down/released. -// Instead special hold events are being triggered when the user is pressing and holding a button for a small period. -// Simulation is activated only for those buttons and remote control that do not have a seperate event already -- (BOOL) simulateHoldEvent; -- (void) setSimulateHoldEvent: (BOOL) value; - -// click counting makes it possible to recognize if the user has pressed a button repeatedly -// click counting does delay each event as it has to wait if there is another event (second click) -// therefore there is a slight time difference (maximumClickCountTimeDifference) between a single click -// of the user and the call of your delegate method -// click counting can be enabled individually for specific buttons. Use the property clickCountEnableButtons to -// set the buttons for which click counting shall be enabled -- (BOOL) clickCountingEnabled; -- (void) setClickCountingEnabled: (BOOL) value; - -- (unsigned int) clickCountEnabledButtons; -- (void) setClickCountEnabledButtons: (unsigned int)value; - -// the maximum time difference till which clicks are recognized as multi clicks -- (NSTimeInterval) maximumClickCountTimeDifference; -- (void) setMaximumClickCountTimeDifference: (NSTimeInterval) timeDiff; - -@end - -/* - * Method definitions for the delegate of the MultiClickRemoteBehavior class - */ -@interface NSObject(MultiClickRemoteBehaviorDelegate) - -- (void) remoteButton: (RemoteControlEventIdentifier)buttonIdentifier pressedDown: (BOOL) pressedDown clickCount: (unsigned int) count; - -@end diff --git a/tools/EventClients/Clients/OSXRemote/RemoteControlWrapper/MultiClickRemoteBehavior.m b/tools/EventClients/Clients/OSXRemote/RemoteControlWrapper/MultiClickRemoteBehavior.m deleted file mode 100644 index 4b40fb3df8..0000000000 --- a/tools/EventClients/Clients/OSXRemote/RemoteControlWrapper/MultiClickRemoteBehavior.m +++ /dev/null @@ -1,210 +0,0 @@ -/***************************************************************************** - * MultiClickRemoteBehavior.m - * RemoteControlWrapper - * - * Created by Martin Kahr on 11.03.06 under a MIT-style license. - * Copyright (c) 2006 martinkahr.com. All rights reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - *****************************************************************************/ - -#import "MultiClickRemoteBehavior.h" - -const NSTimeInterval DEFAULT_MAXIMUM_CLICK_TIME_DIFFERENCE=0.35; -const NSTimeInterval HOLD_RECOGNITION_TIME_INTERVAL=0.4; - -@implementation MultiClickRemoteBehavior - -- (id) init { - if (self = [super init]) { - maxClickTimeDifference = DEFAULT_MAXIMUM_CLICK_TIME_DIFFERENCE; - } - return self; -} - -// Delegates are not retained! -// http://developer.apple.com/documentation/Cocoa/Conceptual/CocoaFundamentals/CommunicatingWithObjects/chapter_6_section_4.html -// Delegating objects do not (and should not) retain their delegates. -// However, clients of delegating objects (applications, usually) are responsible for ensuring that their delegates are around -// to receive delegation messages. To do this, they may have to retain the delegate. -- (void) setDelegate: (id) _delegate { - if (_delegate && [_delegate respondsToSelector:@selector(remoteButton:pressedDown:clickCount:)]==NO) return; - - delegate = _delegate; -} -- (id) delegate { - return delegate; -} - -- (BOOL) simulateHoldEvent { - return simulateHoldEvents; -} -- (void) setSimulateHoldEvent: (BOOL) value { - simulateHoldEvents = value; -} - -- (BOOL) simulatesHoldForButtonIdentifier: (RemoteControlEventIdentifier) identifier remoteControl: (RemoteControl*) remoteControl { - // we do that check only for the normal button identifiers as we would check for hold support for hold events instead - if (identifier > (1 << EVENT_TO_HOLD_EVENT_OFFSET)) return NO; - - return [self simulateHoldEvent] && [remoteControl sendsEventForButtonIdentifier: (identifier << EVENT_TO_HOLD_EVENT_OFFSET)]==NO; -} - -- (BOOL) clickCountingEnabled { - return clickCountEnabledButtons != 0; -} -- (void) setClickCountingEnabled: (BOOL) value { - if (value) { - [self setClickCountEnabledButtons: kRemoteButtonPlus | kRemoteButtonMinus | kRemoteButtonPlay | kRemoteButtonLeft | kRemoteButtonRight | kRemoteButtonMenu]; - } else { - [self setClickCountEnabledButtons: 0]; - } -} - -- (unsigned int) clickCountEnabledButtons { - return clickCountEnabledButtons; -} -- (void) setClickCountEnabledButtons: (unsigned int)value { - clickCountEnabledButtons = value; -} - -- (NSTimeInterval) maximumClickCountTimeDifference { - return maxClickTimeDifference; -} -- (void) setMaximumClickCountTimeDifference: (NSTimeInterval) timeDiff { - maxClickTimeDifference = timeDiff; -} - -- (void) sendSimulatedHoldEvent: (id) time { - BOOL startSimulateHold = NO; - RemoteControlEventIdentifier event = lastHoldEvent; - @synchronized(self) { - startSimulateHold = (lastHoldEvent>0 && lastHoldEventTime == [time doubleValue]); - } - if (startSimulateHold) { - lastEventSimulatedHold = YES; - event = (event << EVENT_TO_HOLD_EVENT_OFFSET); - [delegate remoteButton:event pressedDown: YES clickCount: 1]; - } -} - -- (void) executeClickCountEvent: (NSArray*) values { - RemoteControlEventIdentifier event = [[values objectAtIndex: 0] unsignedIntValue]; - NSTimeInterval eventTimePoint = [[values objectAtIndex: 1] doubleValue]; - - BOOL finishedClicking = NO; - int finalClickCount = eventClickCount; - - @synchronized(self) { - finishedClicking = (event != lastClickCountEvent || eventTimePoint == lastClickCountEventTime); - if (finishedClicking) { - eventClickCount = 0; - lastClickCountEvent = 0; - lastClickCountEventTime = 0; - } - } - - if (finishedClicking) { - [delegate remoteButton:event pressedDown: YES clickCount:finalClickCount]; - // trigger a button release event, too - [NSThread sleepUntilDate: [NSDate dateWithTimeIntervalSinceNow:0.1]]; - [delegate remoteButton:event pressedDown: NO clickCount:finalClickCount]; - } -} - -- (void) sendRemoteButtonEvent: (RemoteControlEventIdentifier) event pressedDown: (BOOL) pressedDown remoteControl: (RemoteControl*) remoteControl { - if (!delegate) return; - - BOOL clickCountingForEvent = ([self clickCountEnabledButtons] & event) == event; - - if ([self simulatesHoldForButtonIdentifier: event remoteControl: remoteControl] && lastClickCountEvent==0) { - if (pressedDown) { - // wait to see if it is a hold - lastHoldEvent = event; - lastHoldEventTime = [NSDate timeIntervalSinceReferenceDate]; - [self performSelector:@selector(sendSimulatedHoldEvent:) - withObject:[NSNumber numberWithDouble:lastHoldEventTime] - afterDelay:HOLD_RECOGNITION_TIME_INTERVAL]; - return; - } else { - if (lastEventSimulatedHold) { - // it was a hold - // send an event for "hold release" - event = (event << EVENT_TO_HOLD_EVENT_OFFSET); - lastHoldEvent = 0; - lastEventSimulatedHold = NO; - - [delegate remoteButton:event pressedDown: pressedDown clickCount:1]; - return; - } else { - RemoteControlEventIdentifier previousEvent = lastHoldEvent; - @synchronized(self) { - lastHoldEvent = 0; - } - - // in case click counting is enabled we have to setup the state for that, too - if (clickCountingForEvent) { - lastClickCountEvent = previousEvent; - lastClickCountEventTime = lastHoldEventTime; - NSNumber* eventNumber; - NSNumber* timeNumber; - eventClickCount = 1; - timeNumber = [NSNumber numberWithDouble:lastClickCountEventTime]; - eventNumber= [NSNumber numberWithUnsignedInt:previousEvent]; - NSTimeInterval diffTime = maxClickTimeDifference-([NSDate timeIntervalSinceReferenceDate]-lastHoldEventTime); - [self performSelector: @selector(executeClickCountEvent:) - withObject: [NSArray arrayWithObjects:eventNumber, timeNumber, nil] - afterDelay: diffTime]; - // we do not return here because we are still in the press-release event - // that will be consumed below - } else { - // trigger the pressed down event that we consumed first - [delegate remoteButton:event pressedDown: YES clickCount:1]; - } - } - } - } - - if (clickCountingForEvent) { - if (pressedDown == NO) return; - - NSNumber* eventNumber; - NSNumber* timeNumber; - @synchronized(self) { - lastClickCountEventTime = [NSDate timeIntervalSinceReferenceDate]; - if (lastClickCountEvent == event) { - eventClickCount = eventClickCount + 1; - } else { - eventClickCount = 1; - } - lastClickCountEvent = event; - timeNumber = [NSNumber numberWithDouble:lastClickCountEventTime]; - eventNumber= [NSNumber numberWithUnsignedInt:event]; - } - [self performSelector: @selector(executeClickCountEvent:) - withObject: [NSArray arrayWithObjects:eventNumber, timeNumber, nil] - afterDelay: maxClickTimeDifference]; - } else { - [delegate remoteButton:event pressedDown: pressedDown clickCount:1]; - } - -} - -@end diff --git a/tools/EventClients/Clients/OSXRemote/RemoteControlWrapper/RemoteControl.h b/tools/EventClients/Clients/OSXRemote/RemoteControlWrapper/RemoteControl.h deleted file mode 100644 index 34e9d5e19d..0000000000 --- a/tools/EventClients/Clients/OSXRemote/RemoteControlWrapper/RemoteControl.h +++ /dev/null @@ -1,102 +0,0 @@ -/***************************************************************************** - * RemoteControl.h - * RemoteControlWrapper - * - * Created by Martin Kahr on 11.03.06 under a MIT-style license. - * Copyright (c) 2006 martinkahr.com. All rights reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - *****************************************************************************/ - -#import <Cocoa/Cocoa.h> - -// notifaction names that are being used to signal that an application wants to -// have access to the remote control device or if the application has finished -// using the remote control device -extern NSString* REQUEST_FOR_REMOTE_CONTROL_NOTIFCATION; -extern NSString* FINISHED_USING_REMOTE_CONTROL_NOTIFICATION; - -// keys used in user objects for distributed notifications -extern NSString* kRemoteControlDeviceName; -extern NSString* kApplicationIdentifier; -extern NSString* kTargetApplicationIdentifier; - -// we have a 6 bit offset to make a hold event out of a normal event -#define EVENT_TO_HOLD_EVENT_OFFSET 6 - -@class RemoteControl; - -typedef enum _RemoteControlEventIdentifier { - // normal events - kRemoteButtonPlus =1<<1, - kRemoteButtonMinus =1<<2, - kRemoteButtonMenu =1<<3, - kRemoteButtonPlay =1<<4, - kRemoteButtonRight =1<<5, - kRemoteButtonLeft =1<<6, - - // hold events - kRemoteButtonPlus_Hold =1<<7, - kRemoteButtonMinus_Hold =1<<8, - kRemoteButtonMenu_Hold =1<<9, - kRemoteButtonPlay_Hold =1<<10, - kRemoteButtonRight_Hold =1<<11, - kRemoteButtonLeft_Hold =1<<12, - - // special events (not supported by all devices) - kRemoteControl_Switched =1<<13, -} RemoteControlEventIdentifier; - -@interface NSObject(RemoteControlDelegate) - -- (void) sendRemoteButtonEvent: (RemoteControlEventIdentifier) event pressedDown: (BOOL) pressedDown remoteControl: (RemoteControl*) remoteControl; - -@end - -/* - Base Interface for Remote Control devices - */ -@interface RemoteControl : NSObject { - id delegate; -} - -// returns nil if the remote control device is not available -- (id) initWithDelegate: (id) remoteControlDelegate; - -- (void) setListeningToRemote: (BOOL) value; -- (BOOL) isListeningToRemote; - -- (BOOL) isOpenInExclusiveMode; -- (void) setOpenInExclusiveMode: (BOOL) value; - -- (IBAction) startListening: (id) sender; -- (IBAction) stopListening: (id) sender; - -// is this remote control sending the given event? -- (BOOL) sendsEventForButtonIdentifier: (RemoteControlEventIdentifier) identifier; - -// sending of notifications between applications -+ (void) sendFinishedNotifcationForAppIdentifier: (NSString*) identifier; -+ (void) sendRequestForRemoteControlNotification; - -// name of the device -+ (const char*) remoteControlDeviceName; - -@end diff --git a/tools/EventClients/Clients/OSXRemote/RemoteControlWrapper/RemoteControl.m b/tools/EventClients/Clients/OSXRemote/RemoteControlWrapper/RemoteControl.m deleted file mode 100644 index bf224dd1d8..0000000000 --- a/tools/EventClients/Clients/OSXRemote/RemoteControlWrapper/RemoteControl.m +++ /dev/null @@ -1,102 +0,0 @@ -/***************************************************************************** - * RemoteControl.m - * RemoteControlWrapper - * - * Created by Martin Kahr on 11.03.06 under a MIT-style license. - * Copyright (c) 2006 martinkahr.com. All rights reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - *****************************************************************************/ - -#import "RemoteControl.h" - -// notifaction names that are being used to signal that an application wants to -// have access to the remote control device or if the application has finished -// using the remote control device -NSString* REQUEST_FOR_REMOTE_CONTROL_NOTIFCATION = @"mac.remotecontrols.RequestForRemoteControl"; -NSString* FINISHED_USING_REMOTE_CONTROL_NOTIFICATION = @"mac.remotecontrols.FinishedUsingRemoteControl"; - -// keys used in user objects for distributed notifications -NSString* kRemoteControlDeviceName = @"RemoteControlDeviceName"; -NSString* kApplicationIdentifier = @"CFBundleIdentifier"; -// bundle identifier of the application that should get access to the remote control -// this key is being used in the FINISHED notification only -NSString* kTargetApplicationIdentifier = @"TargetBundleIdentifier"; - - -@implementation RemoteControl - -// returns nil if the remote control device is not available -- (id) initWithDelegate: (id) _remoteControlDelegate { - if (self = [super init]) { - delegate = _remoteControlDelegate; - } - return self; -} - -- (void) dealloc { - [super dealloc]; -} - -- (void) setListeningToRemote: (BOOL) value { -} -- (BOOL) isListeningToRemote { - return NO; -} - -- (IBAction) startListening: (id) sender { -} -- (IBAction) stopListening: (id) sender { - -} - -- (BOOL) isOpenInExclusiveMode { - return YES; -} -- (void) setOpenInExclusiveMode: (BOOL) value { -} - -- (BOOL) sendsEventForButtonIdentifier: (RemoteControlEventIdentifier) identifier { - return YES; -} - -+ (void) sendDistributedNotification: (NSString*) notificationName targetBundleIdentifier: (NSString*) targetIdentifier { - NSDictionary* userInfo = [NSDictionary dictionaryWithObjectsAndKeys: [NSString stringWithCString:[self remoteControlDeviceName] encoding:NSASCIIStringEncoding], - kRemoteControlDeviceName, [[NSBundle mainBundle] bundleIdentifier], kApplicationIdentifier, - targetIdentifier, kTargetApplicationIdentifier, nil]; - - [[NSDistributedNotificationCenter defaultCenter] postNotificationName:notificationName - object:nil - userInfo:userInfo - deliverImmediately:YES]; -} - -+ (void) sendFinishedNotifcationForAppIdentifier: (NSString*) identifier { - [self sendDistributedNotification:FINISHED_USING_REMOTE_CONTROL_NOTIFICATION targetBundleIdentifier:identifier]; -} -+ (void) sendRequestForRemoteControlNotification { - [self sendDistributedNotification:REQUEST_FOR_REMOTE_CONTROL_NOTIFCATION targetBundleIdentifier:nil]; -} - -+ (const char*) remoteControlDeviceName { - return NULL; -} - -@end diff --git a/tools/EventClients/Clients/OSXRemote/RemoteControlWrapper/RemoteControlContainer.h b/tools/EventClients/Clients/OSXRemote/RemoteControlWrapper/RemoteControlContainer.h deleted file mode 100644 index e665ae1eab..0000000000 --- a/tools/EventClients/Clients/OSXRemote/RemoteControlWrapper/RemoteControlContainer.h +++ /dev/null @@ -1,38 +0,0 @@ -/***************************************************************************** - * RemoteControlContainer.h - * RemoteControlWrapper - * - * Created by Martin Kahr on 11.03.06 under a MIT-style license. - * Copyright (c) 2006 martinkahr.com. All rights reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - *****************************************************************************/ - -#import <Cocoa/Cocoa.h> -#import "RemoteControl.h" - -@interface RemoteControlContainer : RemoteControl { - NSMutableArray* remoteControls; -} - -- (BOOL) instantiateAndAddRemoteControlDeviceWithClass: (Class) clazz; -- (unsigned int) count; - -@end diff --git a/tools/EventClients/Clients/OSXRemote/RemoteControlWrapper/RemoteControlContainer.m b/tools/EventClients/Clients/OSXRemote/RemoteControlWrapper/RemoteControlContainer.m deleted file mode 100644 index 8517ea733e..0000000000 --- a/tools/EventClients/Clients/OSXRemote/RemoteControlWrapper/RemoteControlContainer.m +++ /dev/null @@ -1,114 +0,0 @@ -/***************************************************************************** - * RemoteControlContainer.m - * RemoteControlWrapper - * - * Created by Martin Kahr on 11.03.06 under a MIT-style license. - * Copyright (c) 2006 martinkahr.com. All rights reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - *****************************************************************************/ - -#import "RemoteControlContainer.h" - - -@implementation RemoteControlContainer - -- (id) initWithDelegate: (id) _remoteControlDelegate { - if (self = [super initWithDelegate:_remoteControlDelegate]) { - remoteControls = [[NSMutableArray alloc] init]; - } - return self; -} - -- (void) dealloc { - [self stopListening: self]; - [remoteControls release]; - [super dealloc]; -} - -- (BOOL) instantiateAndAddRemoteControlDeviceWithClass: (Class) clazz { - RemoteControl* remoteControl = [[clazz alloc] initWithDelegate: delegate]; - if (remoteControl) { - [remoteControls addObject: remoteControl]; - [remoteControl addObserver: self forKeyPath:@"listeningToRemote" options:NSKeyValueObservingOptionNew context:nil]; - return YES; - } - return NO; -} - -- (unsigned int) count { - return [remoteControls count]; -} - -- (void) reset { - [self willChangeValueForKey:@"listeningToRemote"]; - [self didChangeValueForKey:@"listeningToRemote"]; -} - -- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { - [self reset]; -} - -- (void) setListeningToRemote: (BOOL) value { - int i; - for(i=0; i < [remoteControls count]; i++) { - [[remoteControls objectAtIndex: i] setListeningToRemote: value]; - } - if (value && value != [self isListeningToRemote]) [self performSelector:@selector(reset) withObject:nil afterDelay:0.01]; -} -- (BOOL) isListeningToRemote { - int i; - for(i=0; i < [remoteControls count]; i++) { - if ([[remoteControls objectAtIndex: i] isListeningToRemote]) { - return YES; - } - } - return NO; -} - -- (IBAction) startListening: (id) sender { - int i; - for(i=0; i < [remoteControls count]; i++) { - [[remoteControls objectAtIndex: i] startListening: sender]; - } -} -- (IBAction) stopListening: (id) sender { - int i; - for(i=0; i < [remoteControls count]; i++) { - [[remoteControls objectAtIndex: i] stopListening: sender]; - } -} - -- (BOOL) isOpenInExclusiveMode { - BOOL mode = YES; - int i; - for(i=0; i < [remoteControls count]; i++) { - mode = mode && ([[remoteControls objectAtIndex: i] isOpenInExclusiveMode]); - } - return mode; -} -- (void) setOpenInExclusiveMode: (BOOL) value { - int i; - for(i=0; i < [remoteControls count]; i++) { - [[remoteControls objectAtIndex: i] setOpenInExclusiveMode:value]; - } -} - -@end diff --git a/tools/EventClients/Clients/OSXRemote/XBMCHelper.h b/tools/EventClients/Clients/OSXRemote/XBMCHelper.h index 4d69a91e19..7f6ae5cdb9 100644 --- a/tools/EventClients/Clients/OSXRemote/XBMCHelper.h +++ b/tools/EventClients/Clients/OSXRemote/XBMCHelper.h @@ -8,11 +8,10 @@ #import <Cocoa/Cocoa.h> #import "xbmcclientwrapper.h" +#import "HIDRemote.h" -@class AppleRemote, MultiClickRemoteBehavior; - -@interface XBMCHelper : NSObject { - AppleRemote* mp_remote_control; +@interface XBMCHelper : NSObject<HIDRemoteDelegate> { + HIDRemote *remote; XBMCClientWrapper* mp_wrapper; NSString* mp_app_path; NSString* mp_home_path; diff --git a/tools/EventClients/Clients/OSXRemote/XBMCHelper.m b/tools/EventClients/Clients/OSXRemote/XBMCHelper.m index 19c67ca5c6..3bc68cbc1c 100644 --- a/tools/EventClients/Clients/OSXRemote/XBMCHelper.m +++ b/tools/EventClients/Clients/OSXRemote/XBMCHelper.m @@ -7,216 +7,297 @@ // #import "XBMCHelper.h" -#import "remotecontrolwrapper/AppleRemote.h" #import "XBMCDebugHelpers.h" +#import "HIDRemote.h" //---------------------------------------------------------------------------- //---------------------------------------------------------------------------- +@interface XBMCHelper (private) + +- (NSString *)buttonNameForButtonCode:(HIDRemoteButtonCode)buttonCode; +- (void) checkAndLaunchApp; + +@end + @implementation XBMCHelper - (id) init{ PRINT_SIGNATURE(); - if( ![super init] ){ - return nil; - } - mp_wrapper = nil; - mp_remote_control = [[[AppleRemote alloc] initWithDelegate: self] retain]; - [mp_remote_control setProcessesBacklog:true]; - [mp_remote_control setOpenInExclusiveMode:true]; - if( ! mp_remote_control ){ - NSException* myException = [NSException - exceptionWithName:@"AppleRemoteInitExecption" - reason:@"AppleRemote could not be initialized" - userInfo:nil]; - @throw myException; - } - [mp_remote_control startListening: self]; - if(![mp_remote_control isListeningToRemote]){ - ELOG(@"Warning: XBMCHelper could not open the IR-Device in exclusive mode. Other remote control apps running?"); + if( (self = [super init]) ){ + if ((remote = [HIDRemote sharedHIDRemote])) + { + [remote setDelegate:self]; + [remote setSimulateHoldEvents:NO]; + //for now, we're using lending of exlusive lock + //kHIDRemoteModeExclusiveAuto isn't working, as we're a background daemon + //one possibility would be to know when XBMC is running. Once we know that, + //we could aquire exclusive lock when it's running, and release _exclusive_ + //access once done + [remote setExclusiveLockLendingEnabled:YES]; + + if ([HIDRemote isCandelairInstallationRequiredForRemoteMode:kHIDRemoteModeExclusive]) + { + //setup failed. user needs to install CandelaIR driver + NSLog(@"Error! Candelair driver installation necessary. XBMC Remote control won't function properly!"); + NSLog(@"Due to an issue in the OS version you are running, an additional driver needs to be installed before XBMC(Helper) can reliably access the remote."); + NSLog(@"See http://www.candelair.com/download/ for details"); + [super dealloc]; + return nil; + } + else + { + if ([remote startRemoteControl:kHIDRemoteModeExclusiveAuto]) + { + DLOG(@"Driver has started successfully."); + if ([remote activeRemoteControlCount]) + DLOG(@"Driver has found %d remotes.", [remote activeRemoteControlCount]); + else + ELOG(@"Driver has not found any remotes it could use. Will use remotes as they become available."); + } + else + { + //setup failed, cleanup + [remote setDelegate:nil]; + [super dealloc]; + return nil; + } + } + } } return self; } +//---------------------------------------------------------------------------- - (void) dealloc{ PRINT_SIGNATURE(); - [mp_remote_control stopListening: self]; - [mp_remote_control release]; + [remote stopRemoteControl]; [mp_wrapper release]; [mp_app_path release]; [mp_home_path release]; + [super dealloc]; } //---------------------------------------------------------------------------- -- (void) checkAndLaunchApp -{ - if(!mp_app_path || ![mp_app_path length]){ - ELOG(@"No executable set. Nothing to launch"); - return; - } - - NSFileManager *fileManager = [NSFileManager defaultManager]; - if(![fileManager fileExistsAtPath:mp_app_path]){ - ELOG(@"Path does not exist: %@. Cannot launch executable", mp_app_path); - return; - } - if(mp_home_path && [mp_home_path length]) - setenv("XBMC_HOME", [mp_home_path cString], 1); - //launch or activate xbmc - if(![[NSWorkspace sharedWorkspace] launchApplication:mp_app_path]){ - ELOG(@"Error launching %@", mp_app_path); +- (void) connectToServer:(NSString*) fp_server onPort:(int) f_port withMode:(eRemoteMode) f_mode withTimeout:(double) f_timeout{ + if(mp_wrapper) + [self disconnect]; + mp_wrapper = [[XBMCClientWrapper alloc] initWithMode:f_mode serverAddress:fp_server port:f_port verbose:m_verbose]; + [mp_wrapper setUniversalModeTimeout:f_timeout]; +} + +//---------------------------------------------------------------------------- +- (void) disconnect{ + [mp_wrapper release]; + mp_wrapper = nil; +} + +//---------------------------------------------------------------------------- +- (void) enableVerboseMode:(bool) f_really{ + m_verbose = f_really; + [mp_wrapper enableVerboseMode:f_really]; +} + +//---------------------------------------------------------------------------- +- (void) setApplicationPath:(NSString*) fp_app_path{ + if (mp_app_path != fp_app_path) { + [mp_app_path release]; + mp_app_path = [[fp_app_path stringByStandardizingPath] retain]; } } //---------------------------------------------------------------------------- -- (void) sendRemoteButtonEvent: (RemoteControlEventIdentifier) event pressedDown: (BOOL) pressedDown remoteControl: (RemoteControl*) remoteControl; +- (void) setApplicationHome:(NSString*) fp_home_path{ + if (mp_home_path != fp_home_path) { + [mp_home_path release]; + mp_home_path = [[fp_home_path stringByStandardizingPath] retain]; + } +} + +#pragma mark - +#pragma mark HIDRemote delegate methods + +// 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) { - if(m_verbose) { - //do some logging here - //[self logButton: event press; + if(m_verbose){ + NSLog(@"Received button '%@' %@ event", [self buttonNameForButtonCode:buttonCode], (isPressed)?@"press":@"release"); } - - switch(event){ - case kRemoteButtonPlay: - if(pressedDown) [mp_wrapper handleEvent:ATV_BUTTON_PLAY]; - break; - case kRemoteButtonPlay_Hold: - if(pressedDown) [mp_wrapper handleEvent:ATV_BUTTON_PLAY_H]; - break; - case kRemoteButtonRight: - if(pressedDown) - [mp_wrapper handleEvent:ATV_BUTTON_RIGHT]; + switch(buttonCode) + { + case kHIDRemoteButtonCodePlus: + if(isPressed) + [mp_wrapper handleEvent:ATV_BUTTON_UP]; else - [mp_wrapper handleEvent:ATV_BUTTON_RIGHT_RELEASE]; + [mp_wrapper handleEvent:ATV_BUTTON_UP_RELEASE]; break; - case kRemoteButtonRight_Hold: - if(pressedDown) - [mp_wrapper handleEvent:ATV_BUTTON_RIGHT_H]; + case kHIDRemoteButtonCodeMinus: + if(isPressed) + [mp_wrapper handleEvent:ATV_BUTTON_DOWN]; else - [mp_wrapper handleEvent:ATV_BUTTON_RIGHT_H_RELEASE]; + [mp_wrapper handleEvent:ATV_BUTTON_DOWN_RELEASE]; break; - case kRemoteButtonLeft: - if(pressedDown) + case kHIDRemoteButtonCodeLeft: + if(isPressed) [mp_wrapper handleEvent:ATV_BUTTON_LEFT]; else [mp_wrapper handleEvent:ATV_BUTTON_LEFT_RELEASE]; break; - case kRemoteButtonLeft_Hold: - if(pressedDown) + case kHIDRemoteButtonCodeLeftHold: + if(isPressed) [mp_wrapper handleEvent:ATV_BUTTON_LEFT_H]; else [mp_wrapper handleEvent:ATV_BUTTON_LEFT_H_RELEASE]; break; - case kRemoteButtonPlus: - if(pressedDown) - [mp_wrapper handleEvent:ATV_BUTTON_UP]; + case kHIDRemoteButtonCodeRight: + if(isPressed) + [mp_wrapper handleEvent:ATV_BUTTON_RIGHT]; else - [mp_wrapper handleEvent:ATV_BUTTON_UP_RELEASE]; + [mp_wrapper handleEvent:ATV_BUTTON_RIGHT_RELEASE]; break; - case kRemoteButtonMinus: - if(pressedDown) - [mp_wrapper handleEvent:ATV_BUTTON_DOWN]; + case kHIDRemoteButtonCodeRightHold: + if(isPressed) + [mp_wrapper handleEvent:ATV_BUTTON_RIGHT_H]; else - [mp_wrapper handleEvent:ATV_BUTTON_DOWN_RELEASE]; - break; - case kRemoteButtonMenu: - if(pressedDown){ - [self checkAndLaunchApp]; //launch mp_app_path if it's not running + [mp_wrapper handleEvent:ATV_BUTTON_RIGHT_H_RELEASE]; + break; + case kHIDRemoteButtonCodePlayPause: + if(isPressed) [mp_wrapper handleEvent:ATV_BUTTON_PLAY]; + break; + case kHIDRemoteButtonCodePlayPauseHold: + if(isPressed) [mp_wrapper handleEvent:ATV_BUTTON_PLAY_H]; + break; + case kHIDRemoteButtonCodeMenu: + if(isPressed){ [mp_wrapper handleEvent:ATV_BUTTON_MENU]; } break; - case kRemoteButtonMenu_Hold: - if(pressedDown) [mp_wrapper handleEvent:ATV_BUTTON_MENU_H]; - break; - case kRemoteControl_Switched: - if(pressedDown) [mp_wrapper switchRemote: [mp_remote_control deviceID]]; - break; + case kHIDRemoteButtonCodeMenuHold: + if(isPressed) { + [self checkAndLaunchApp]; //launch mp_app_path if it's not running + [mp_wrapper handleEvent:ATV_BUTTON_MENU_H]; + } + break; default: - NSLog(@"Oha, remote button not recognized %i pressed/released %i", event, pressedDown); + NSLog(@"Oha, remote button not recognized %i pressed/released %i", buttonCode, isPressed); } } -//---------------------------------------------------------------------------- -- (void) connectToServer:(NSString*) fp_server onPort:(int) f_port withMode:(eRemoteMode) f_mode withTimeout:(double) f_timeout{ - if(mp_wrapper) - [self disconnect]; - mp_wrapper = [[XBMCClientWrapper alloc] initWithMode:f_mode serverAddress:fp_server port:f_port verbose:m_verbose]; - [mp_wrapper setUniversalModeTimeout:f_timeout]; -} -//---------------------------------------------------------------------------- -- (void) disconnect{ - [mp_wrapper release]; - mp_wrapper = nil; +// Notification of ID changes +- (void)hidRemote:(HIDRemote *)hidRemote +remoteIDChangedOldID:(SInt32)old // Invoked when the user switched to a remote control with a different ID + newID:(SInt32)newID +{ + if(m_verbose) + NSLog(@"Change of remote ID from %d to %d", old, newID); + [mp_wrapper switchRemote: newID]; } -//---------------------------------------------------------------------------- -- (void) enableVerboseMode:(bool) f_really{ - m_verbose = f_really; - [mp_wrapper enableVerboseMode:f_really]; -} +#pragma mark - +#pragma mark Helper methods -//---------------------------------------------------------------------------- -- (void) setApplicationPath:(NSString*) fp_app_path{ - if (mp_app_path != fp_app_path) { - [mp_app_path release]; - mp_app_path = [[fp_app_path stringByStandardizingPath] retain]; - } +- (NSString *)buttonNameForButtonCode:(HIDRemoteButtonCode)buttonCode +{ + switch (buttonCode) + { + case kHIDRemoteButtonCodePlus: + return (@"Plus"); + break; + case kHIDRemoteButtonCodeMinus: + return (@"Minus"); + break; + case kHIDRemoteButtonCodeLeft: + return (@"Left"); + break; + case kHIDRemoteButtonCodeRight: + return (@"Right"); + break; + case kHIDRemoteButtonCodePlayPause: + return (@"Play/Pause"); + break; + case kHIDRemoteButtonCodeMenu: + return (@"Menu"); + break; + case kHIDRemoteButtonCodePlusHold: + return (@"Plus (hold)"); + break; + case kHIDRemoteButtonCodeMinusHold: + return (@"Minus (hold)"); + break; + case kHIDRemoteButtonCodeLeftHold: + return (@"Left (hold)"); + break; + case kHIDRemoteButtonCodeRightHold: + return (@"Right (hold)"); + break; + case kHIDRemoteButtonCodePlayPauseHold: + return (@"Play/Pause (hold)"); + break; + case kHIDRemoteButtonCodeMenuHold: + return (@"Menu (hold)"); + break; + } + return ([NSString stringWithFormat:@"Button %x", (int)buttonCode]); } //---------------------------------------------------------------------------- -- (void) setApplicationHome:(NSString*) fp_home_path{ - if (mp_home_path != fp_home_path) { - [mp_home_path release]; - mp_home_path = [[fp_home_path stringByStandardizingPath] retain]; +- (void) checkAndLaunchApp +{ + if(!mp_app_path || ![mp_app_path length]){ + ELOG(@"No executable set. Nothing to launch"); + return; } + NSFileManager *fileManager = [NSFileManager defaultManager]; + if([fileManager fileExistsAtPath:mp_app_path]){ + if(mp_home_path && [mp_home_path length]) + setenv("XBMC_HOME", [mp_home_path cString], 1); + //launch or activate xbmc + if(![[NSWorkspace sharedWorkspace] launchApplication:mp_app_path]) + ELOG(@"Error launching %@", mp_app_path); + } else + ELOG(@"Path does not exist: %@. Cannot launch executable", mp_app_path); } -// NSString* pressed; -// NSString* buttonName; -// if (pressedDown) pressed = @"(pressed)"; else pressed = @"(released)"; -// -// switch(event) { -// case kRemoteButtonPlus: -// buttonName = @"Volume up"; -// break; -// case kRemoteButtonMinus: -// buttonName = @"Volume down"; -// break; -// case kRemoteButtonMenu: -// buttonName = @"Menu"; -// break; -// case kRemoteButtonPlay: -// buttonName = @"Play"; -// break; -// case kRemoteButtonRight: -// buttonName = @"Right"; -// break; -// case kRemoteButtonLeft: -// buttonName = @"Left"; -// break; -// case kRemoteButtonRight_Hold: -// buttonName = @"Right holding"; -// break; -// case kRemoteButtonLeft_Hold: -// buttonName = @"Left holding"; -// break; -// case kRemoteButtonPlus_Hold: -// buttonName = @"Volume up holding"; -// break; -// case kRemoteButtonMinus_Hold: -// buttonName = @"Volume down holding"; -// break; -// case kRemoteButtonPlay_Hold: -// buttonName = @"Play (sleep mode)"; -// break; -// case kRemoteButtonMenu_Hold: -// buttonName = @"Menu holding"; -// break; -// case kRemoteControl_Switched: -// buttonName = @"Remote Control Switched"; -// break; -// default: -// break; -// } -// NSLog(@"%@ %@", pressed, buttonName); -// } + + +#pragma mark - +#pragma mark Other (unused) HIDRemoteDelegate methods +//- (BOOL)hidRemote:(HIDRemote *)aHidRemote +//lendExclusiveLockToApplicationWithInfo:(NSDictionary *)applicationInfo +//{ +// NSLog(@"Lending exclusive lock to %@ (pid %@)", [applicationInfo objectForKey:(id)kCFBundleIdentifierKey], [applicationInfo objectForKey:kHIDRemoteDNStatusPIDKey]); +// return (YES); +//} +// +//- (void)hidRemote:(HIDRemote *)aHidRemote +//exclusiveLockReleasedByApplicationWithInfo:(NSDictionary *)applicationInfo +//{ +// NSLog(@"Exclusive lock released by %@ (pid %@)", [applicationInfo objectForKey:(id)kCFBundleIdentifierKey], [applicationInfo objectForKey:kHIDRemoteDNStatusPIDKey]); +// [aHidRemote startRemoteControl:kHIDRemoteModeExclusive]; +//} +// +//- (BOOL)hidRemote:(HIDRemote *)aHidRemote +//shouldRetryExclusiveLockWithInfo:(NSDictionary *)applicationInfo +//{ +// NSLog(@"%@ (pid %@) says I should retry to acquire exclusive locks", [applicationInfo objectForKey:(id)kCFBundleIdentifierKey], [applicationInfo objectForKey:kHIDRemoteDNStatusPIDKey]); +// return (YES); +//} +// +// +//// Notification about hardware additions/removals +//- (void)hidRemote:(HIDRemote *)aHidRemote foundNewHardwareWithAttributes:(NSMutableDictionary *)attributes +//{ +// NSLog(@"Found hardware: %@ by %@ (Transport: %@)", [attributes objectForKey:kHIDRemoteProduct], [attributes objectForKey:kHIDRemoteManufacturer], [attributes objectForKey:kHIDRemoteTransport]); +//} +// +//- (void)hidRemote:(HIDRemote *)aHidRemote failedNewHardwareWithError:(NSError *)error +//{ +// NSLog(@"Initialization of hardware failed with error %@ (%@)", [error localizedDescription], [[error userInfo] objectForKey:@"InternalErrorCode"]); +//} +// +//- (void)hidRemote:(HIDRemote *)aHidRemote releasedHardwareWithAttributes:(NSMutableDictionary *)attributes +//{ +// NSLog(@"Released hardware: %@ by %@ (Transport: %@)", [attributes objectForKey:kHIDRemoteProduct], [attributes objectForKey:kHIDRemoteManufacturer], [attributes objectForKey:kHIDRemoteTransport]); +//} @end diff --git a/tools/EventClients/Clients/OSXRemote/XBMCHelper.xcodeproj/project.pbxproj b/tools/EventClients/Clients/OSXRemote/XBMCHelper.xcodeproj/project.pbxproj index 0c6c5ef7c9..a3686c5ceb 100644 --- a/tools/EventClients/Clients/OSXRemote/XBMCHelper.xcodeproj/project.pbxproj +++ b/tools/EventClients/Clients/OSXRemote/XBMCHelper.xcodeproj/project.pbxproj @@ -7,16 +7,9 @@ objects = { /* Begin PBXBuildFile section */ - E45F2B1C0F95191E007BCC0B /* IRKeyboardEmu.m in Sources */ = {isa = PBXBuildFile; fileRef = E45F2B1B0F95191E007BCC0B /* IRKeyboardEmu.m */; }; + E424E22C10877D3700659D45 /* HIDRemote.m in Sources */ = {isa = PBXBuildFile; fileRef = E424E22B10877D3700659D45 /* HIDRemote.m */; }; E4E62F370F83DB760066AF9D /* xbmchelper_main.mm in Sources */ = {isa = PBXBuildFile; fileRef = E4E62F340F83DB760066AF9D /* xbmchelper_main.mm */; }; E4E62F380F83DB760066AF9D /* XBMCHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = E4E62F360F83DB760066AF9D /* XBMCHelper.m */; }; - E4E62F4A0F83DB830066AF9D /* AppleRemote.m in Sources */ = {isa = PBXBuildFile; fileRef = E4E62F3D0F83DB830066AF9D /* AppleRemote.m */; }; - E4E62F4B0F83DB830066AF9D /* GlobalKeyboardDevice.m in Sources */ = {isa = PBXBuildFile; fileRef = E4E62F3F0F83DB830066AF9D /* GlobalKeyboardDevice.m */; }; - E4E62F4C0F83DB830066AF9D /* HIDRemoteControlDevice.m in Sources */ = {isa = PBXBuildFile; fileRef = E4E62F410F83DB830066AF9D /* HIDRemoteControlDevice.m */; }; - E4E62F4D0F83DB830066AF9D /* KeyspanFrontRowControl.m in Sources */ = {isa = PBXBuildFile; fileRef = E4E62F430F83DB830066AF9D /* KeyspanFrontRowControl.m */; }; - E4E62F4E0F83DB830066AF9D /* MultiClickRemoteBehavior.m in Sources */ = {isa = PBXBuildFile; fileRef = E4E62F450F83DB830066AF9D /* MultiClickRemoteBehavior.m */; }; - E4E62F4F0F83DB830066AF9D /* RemoteControl.m in Sources */ = {isa = PBXBuildFile; fileRef = E4E62F470F83DB830066AF9D /* RemoteControl.m */; }; - E4E62F500F83DB830066AF9D /* RemoteControlContainer.m in Sources */ = {isa = PBXBuildFile; fileRef = E4E62F490F83DB830066AF9D /* RemoteControlContainer.m */; }; E4E62F600F83FB8C0066AF9D /* IOKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E4E62F5F0F83FB8C0066AF9D /* IOKit.framework */; }; E4E62F690F83FBB40066AF9D /* Carbon.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E4E62F680F83FBB40066AF9D /* Carbon.framework */; }; E4E62FD40F83FD7C0066AF9D /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E4E62FD30F83FD7C0066AF9D /* Cocoa.framework */; }; @@ -38,25 +31,12 @@ /* Begin PBXFileReference section */ 09AB6884FE841BABC02AAC07 /* CoreFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreFoundation.framework; path = /System/Library/Frameworks/CoreFoundation.framework; sourceTree = "<absolute>"; }; 8DD76F7E0486A8DE00D96B5E /* XBMCHelper */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = XBMCHelper; sourceTree = BUILT_PRODUCTS_DIR; }; - E45F2B1A0F95191E007BCC0B /* IRKeyboardEmu.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IRKeyboardEmu.h; sourceTree = "<group>"; }; - E45F2B1B0F95191E007BCC0B /* IRKeyboardEmu.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IRKeyboardEmu.m; sourceTree = "<group>"; }; + E424E22A10877D3700659D45 /* HIDRemote.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HIDRemote.h; sourceTree = "<group>"; }; + E424E22B10877D3700659D45 /* HIDRemote.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HIDRemote.m; sourceTree = "<group>"; }; + E424E23710877D5400659D45 /* Makefile.in */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = Makefile.in; sourceTree = "<group>"; }; E4E62F340F83DB760066AF9D /* xbmchelper_main.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = xbmchelper_main.mm; sourceTree = "<group>"; }; E4E62F350F83DB760066AF9D /* XBMCHelper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XBMCHelper.h; sourceTree = "<group>"; }; E4E62F360F83DB760066AF9D /* XBMCHelper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = XBMCHelper.m; sourceTree = "<group>"; }; - E4E62F3C0F83DB830066AF9D /* AppleRemote.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppleRemote.h; sourceTree = "<group>"; }; - E4E62F3D0F83DB830066AF9D /* AppleRemote.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppleRemote.m; sourceTree = "<group>"; }; - E4E62F3E0F83DB830066AF9D /* GlobalKeyboardDevice.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GlobalKeyboardDevice.h; sourceTree = "<group>"; }; - E4E62F3F0F83DB830066AF9D /* GlobalKeyboardDevice.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GlobalKeyboardDevice.m; sourceTree = "<group>"; }; - E4E62F400F83DB830066AF9D /* HIDRemoteControlDevice.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HIDRemoteControlDevice.h; sourceTree = "<group>"; }; - E4E62F410F83DB830066AF9D /* HIDRemoteControlDevice.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HIDRemoteControlDevice.m; sourceTree = "<group>"; }; - E4E62F420F83DB830066AF9D /* KeyspanFrontRowControl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KeyspanFrontRowControl.h; sourceTree = "<group>"; }; - E4E62F430F83DB830066AF9D /* KeyspanFrontRowControl.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = KeyspanFrontRowControl.m; sourceTree = "<group>"; }; - E4E62F440F83DB830066AF9D /* MultiClickRemoteBehavior.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MultiClickRemoteBehavior.h; sourceTree = "<group>"; }; - E4E62F450F83DB830066AF9D /* MultiClickRemoteBehavior.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MultiClickRemoteBehavior.m; sourceTree = "<group>"; }; - E4E62F460F83DB830066AF9D /* RemoteControl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RemoteControl.h; sourceTree = "<group>"; }; - E4E62F470F83DB830066AF9D /* RemoteControl.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RemoteControl.m; sourceTree = "<group>"; }; - E4E62F480F83DB830066AF9D /* RemoteControlContainer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RemoteControlContainer.h; sourceTree = "<group>"; }; - E4E62F490F83DB830066AF9D /* RemoteControlContainer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RemoteControlContainer.m; sourceTree = "<group>"; }; E4E62F5F0F83FB8C0066AF9D /* IOKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = IOKit.framework; path = System/Library/Frameworks/IOKit.framework; sourceTree = SDKROOT; }; E4E62F680F83FBB40066AF9D /* Carbon.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Carbon.framework; path = System/Library/Frameworks/Carbon.framework; sourceTree = SDKROOT; }; E4E62FD30F83FD7C0066AF9D /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; @@ -83,6 +63,7 @@ 08FB7794FE84155DC02AAC07 /* XBMCHelper */ = { isa = PBXGroup; children = ( + E424E23710877D5400659D45 /* Makefile.in */, 08FB7795FE84155DC02AAC07 /* Source */, C6859E96029091FE04C91782 /* Documentation */, 08FB779DFE84155DC02AAC07 /* External Frameworks and Libraries */, @@ -97,7 +78,8 @@ 08FB7795FE84155DC02AAC07 /* Source */ = { isa = PBXGroup; children = ( - E4E62F3B0F83DB830066AF9D /* RemoteControlWrapper */, + E424E22A10877D3700659D45 /* HIDRemote.h */, + E424E22B10877D3700659D45 /* HIDRemote.m */, E4E630030F8406900066AF9D /* xbmcclient.h */, E4E62F340F83DB760066AF9D /* xbmchelper_main.mm */, E4E62F350F83DB760066AF9D /* XBMCHelper.h */, @@ -132,29 +114,6 @@ name = Documentation; sourceTree = "<group>"; }; - E4E62F3B0F83DB830066AF9D /* RemoteControlWrapper */ = { - isa = PBXGroup; - children = ( - E4E62F3C0F83DB830066AF9D /* AppleRemote.h */, - E4E62F3D0F83DB830066AF9D /* AppleRemote.m */, - E4E62F3E0F83DB830066AF9D /* GlobalKeyboardDevice.h */, - E4E62F3F0F83DB830066AF9D /* GlobalKeyboardDevice.m */, - E4E62F400F83DB830066AF9D /* HIDRemoteControlDevice.h */, - E4E62F410F83DB830066AF9D /* HIDRemoteControlDevice.m */, - E4E62F420F83DB830066AF9D /* KeyspanFrontRowControl.h */, - E4E62F430F83DB830066AF9D /* KeyspanFrontRowControl.m */, - E4E62F440F83DB830066AF9D /* MultiClickRemoteBehavior.h */, - E4E62F450F83DB830066AF9D /* MultiClickRemoteBehavior.m */, - E4E62F460F83DB830066AF9D /* RemoteControl.h */, - E4E62F470F83DB830066AF9D /* RemoteControl.m */, - E4E62F480F83DB830066AF9D /* RemoteControlContainer.h */, - E4E62F490F83DB830066AF9D /* RemoteControlContainer.m */, - E45F2B1A0F95191E007BCC0B /* IRKeyboardEmu.h */, - E45F2B1B0F95191E007BCC0B /* IRKeyboardEmu.m */, - ); - path = RemoteControlWrapper; - sourceTree = "<group>"; - }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -200,15 +159,8 @@ files = ( E4E62F370F83DB760066AF9D /* xbmchelper_main.mm in Sources */, E4E62F380F83DB760066AF9D /* XBMCHelper.m in Sources */, - E4E62F4A0F83DB830066AF9D /* AppleRemote.m in Sources */, - E4E62F4B0F83DB830066AF9D /* GlobalKeyboardDevice.m in Sources */, - E4E62F4C0F83DB830066AF9D /* HIDRemoteControlDevice.m in Sources */, - E4E62F4D0F83DB830066AF9D /* KeyspanFrontRowControl.m in Sources */, - E4E62F4E0F83DB830066AF9D /* MultiClickRemoteBehavior.m in Sources */, - E4E62F4F0F83DB830066AF9D /* RemoteControl.m in Sources */, - E4E62F500F83DB830066AF9D /* RemoteControlContainer.m in Sources */, E4E62FE80F83FDD90066AF9D /* xbmcclientwrapper.mm in Sources */, - E45F2B1C0F95191E007BCC0B /* IRKeyboardEmu.m in Sources */, + E424E22C10877D3700659D45 /* HIDRemote.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/tools/EventClients/Clients/OSXRemote/xbmchelper_main.mm b/tools/EventClients/Clients/OSXRemote/xbmchelper_main.mm index feb23473f1..55699821d7 100644 --- a/tools/EventClients/Clients/OSXRemote/xbmchelper_main.mm +++ b/tools/EventClients/Clients/OSXRemote/xbmchelper_main.mm @@ -21,7 +21,7 @@ bool g_verbose_mode = false; // const char* PROGNAME="XBMCHelper"; -const char* PROGVERS="0.5"; +const char* PROGVERS="0.6"; void ParseOptions(int argc, char** argv); void ReadConfig(); @@ -199,19 +199,23 @@ int main (int argc, char * argv[]) { NSLog(@"%s %s starting up...", PROGNAME, PROGVERS); gp_xbmchelper = [[XBMCHelper alloc] init]; - - signal(SIGHUP, Reconfigure); - signal(SIGINT, Reconfigure); - signal(SIGTERM, Reconfigure); - - ParseOptions(argc,argv); - StartHelper(); - - //run event loop in this thread - RunCurrentEventLoop(kEventDurationForever); - NSLog(@"%s %s exiting...", PROGNAME, PROGVERS); - //cleanup - [gp_xbmchelper release]; + if(gp_xbmchelper){ + signal(SIGHUP, Reconfigure); + signal(SIGINT, Reconfigure); + signal(SIGTERM, Reconfigure); + + ParseOptions(argc,argv); + StartHelper(); + + //run event loop in this thread + RunCurrentEventLoop(kEventDurationForever); + NSLog(@"%s %s exiting...", PROGNAME, PROGVERS); + //cleanup + [gp_xbmchelper release]; + } else { + NSLog(@"%s %s failed to initialize remote.", PROGNAME, PROGVERS); + return -1; + } [pool drain]; return 0; } |