LCOV - code coverage report
Current view: top level - Cocoa - OOMacJoystickManager.m (source / functions) Hit Total Coverage
Test: coverxygen.info Lines: 0 15 0.0 %
Date: 2025-05-28 07:50:54 Functions: 0 0 -

          Line data    Source code
       1           0 : /*
       2             : 
       3             : OOMacJoystickManager.m
       4             : By Alex Smith and Jens Ayton
       5             : 
       6             : Oolite
       7             : Copyright (C) 2004-2013 Giles C Williams and contributors
       8             : 
       9             : This program is free software; you can redistribute it and/or
      10             : modify it under the terms of the GNU General Public License
      11             : as published by the Free Software Foundation; either version 2
      12             : of the License, or (at your option) any later version.
      13             : 
      14             : This program is distributed in the hope that it will be useful,
      15             : but WITHOUT ANY WARRANTY; without even the implied warranty of
      16             : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      17             : GNU General Public License for more details.
      18             : 
      19             : You should have received a copy of the GNU General Public License
      20             : along with this program; if not, write to the Free Software
      21             : Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
      22             : MA 02110-1301, USA.
      23             : 
      24             : */
      25             : 
      26             : #import "OOMacJoystickManager.h"
      27             : #import "OOLogging.h"
      28             : #import "OOCollectionExtractors.h"
      29             : 
      30             : 
      31             : @interface OOMacJoystickManager ()
      32             : 
      33             : - (void) handleInputEvent:(IOHIDValueRef)value;
      34             : - (void) handleJoystickAttach:(IOHIDDeviceRef)device;
      35             : - (void) handleDeviceRemoval:(IOHIDDeviceRef)device;
      36             : 
      37             : @end
      38             : 
      39             : 
      40             : static void HandleDeviceMatchingCallback(void * inContext, IOReturn inResult, void* inSender, IOHIDDeviceRef  inIOHIDDeviceRef);
      41             : static void HandleInputValueCallback(void * inContext, IOReturn inResult, void* inSender, IOHIDValueRef  inIOHIDValueRef);
      42             : static void HandleDeviceRemovalCallback(void * inContext, IOReturn inResult, void* inSender, IOHIDDeviceRef  inIOHIDDeviceRef);
      43             : 
      44             : 
      45             : @implementation OOMacJoystickManager
      46             : 
      47           0 : - (id) init
      48             : {
      49             :         if ((self = [super init]))
      50             :         {
      51             :                 // Initialise gamma table
      52             :                 int i;
      53             :                 for (i = 0; i< kJoystickGammaTableSize; i++)
      54             :                 {
      55             :                         double x = ((double) i - 128.0) / 128.0;
      56             :                         double sign = x>=0 ? 1.0 : -1.0;
      57             :                         double y = sign * floor(32767.0 * pow (fabs(x), STICK_GAMMA)); 
      58             :                         gammaTable[i] = (int) y;
      59             :                 }
      60             :                 
      61             :                 /*
      62             :                         SLOW_CODE
      63             :                         The call to IOHIDManagerSetDeviceMatching() has a significiant
      64             :                         presence in warm startup profiles; sometimes it's even makes
      65             :                         -[OOMacJoystickManager init] the single heaviest Oolite symbol
      66             :                         within -[GameController applicationDidFinishLaunching]. Setup
      67             :                         should be made asynchronous.
      68             :                         -- Ahruman 2012-09-14
      69             :                 */
      70             :                 hidManager = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone);
      71             :                 IOHIDManagerSetDeviceMatching(hidManager, NULL);
      72             :                 
      73             :                 IOHIDManagerRegisterDeviceMatchingCallback(hidManager, HandleDeviceMatchingCallback, self);
      74             :                 IOHIDManagerRegisterDeviceRemovalCallback(hidManager, HandleDeviceRemovalCallback, self);
      75             :                 
      76             :                 IOHIDManagerScheduleWithRunLoop(hidManager, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
      77             :                 IOReturn iores = IOHIDManagerOpen(hidManager, kIOHIDOptionsTypeNone);
      78             :                 if (iores != kIOReturnSuccess)
      79             :                 {
      80             :                         OOLog(@"joystick.error.init", @"Cannot open HID manager; joystick support will not function.");
      81             :                 }
      82             :                 
      83             :                 devices = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
      84             :         }
      85             :         return self;
      86             : }
      87             : 
      88             : 
      89           0 : - (void) dealloc
      90             : {
      91             :         if (hidManager != NULL)  CFRelease(hidManager);
      92             :         if (devices != NULL)  CFRelease(devices);
      93             :         
      94             :         [super dealloc];
      95             : }
      96             : 
      97             : 
      98           0 : - (NSUInteger) joystickCount
      99             : {
     100             :         return CFArrayGetCount(devices);
     101             : }
     102             : 
     103             : 
     104           0 : - (void) handleDeviceAttach:(IOHIDDeviceRef)device
     105             : {
     106             :         NSArray *usagePairs = (NSArray *)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDDeviceUsagePairsKey));
     107             :         
     108             :         for (NSDictionary *pair in usagePairs)
     109             :         {
     110             :                 if ([pair oo_unsignedIntForKey:@kIOHIDDeviceUsagePageKey] == kHIDPage_GenericDesktop)
     111             :                 {
     112             :                         unsigned usage = [pair oo_unsignedIntForKey:@kIOHIDDeviceUsageKey];
     113             :                         if (usage == kHIDUsage_GD_Joystick || usage == kHIDUsage_GD_GamePad)
     114             :                         {
     115             :                                 [self handleJoystickAttach:device];
     116             :                                 return;
     117             :                         }
     118             :                 }
     119             :         }
     120             :         
     121             :         if (OOLogWillDisplayMessagesInClass(@"joystick.reject"))
     122             :         {
     123             :                 NSString *product = (NSString *)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductKey));
     124             :                 unsigned usagePage = [(NSNumber *)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDPrimaryUsagePageKey)) unsignedIntValue];
     125             :                 unsigned usage = [(NSNumber *)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDPrimaryUsageKey)) unsignedIntValue];
     126             :                 OOLog(@"joystick.reject", @"Ignoring HID device: %@ (primary usage %u:%u)", product, usagePage, usage);
     127             :         }
     128             : }
     129             : 
     130             : 
     131           0 : - (void) handleJoystickAttach:(IOHIDDeviceRef)device
     132             : {
     133             :         OOLog(@"joystick.connect", @"Joystick connected: %@", IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductKey)));
     134             :         
     135             :         CFArrayAppendValue(devices, device);
     136             :         IOHIDDeviceRegisterInputValueCallback(device, HandleInputValueCallback, self);
     137             :         
     138             :         if (OOLogWillDisplayMessagesInClass(@"joystick.connect.element"))
     139             :         {
     140             :                 // Print out elements of new device
     141             :                 CFArrayRef elementList = IOHIDDeviceCopyMatchingElements(device, NULL, 0L);
     142             :                 if (elementList != NULL)
     143             :                 {
     144             :                         OOLogIndent();
     145             :                         
     146             :                         CFIndex idx, count = CFArrayGetCount(elementList);
     147             :                         
     148             :                         for (idx = 0; idx < count; idx++)
     149             :                         {
     150             :                                 IOHIDElementRef element = (IOHIDElementRef)CFArrayGetValueAtIndex(elementList, idx);
     151             :                                 IOHIDElementType elementType = IOHIDElementGetType(element);
     152             :                                 if (elementType > kIOHIDElementTypeInput_ScanCodes)
     153             :                                 {
     154             :                                         continue;
     155             :                                 }
     156             :                                 IOHIDElementCookie elementCookie = IOHIDElementGetCookie(element);
     157             :                                 uint32_t usagePage = IOHIDElementGetUsagePage(element);
     158             :                                 uint32_t usage = IOHIDElementGetUsage(element);
     159             :                                 uint32_t min = (uint32_t)IOHIDElementGetPhysicalMin(element);
     160             :                                 uint32_t max = (uint32_t)IOHIDElementGetPhysicalMax(element);
     161             :                                 NSString *name = (NSString *)IOHIDElementGetProperty(element, CFSTR(kIOHIDElementNameKey)) ?: @"unnamed";
     162             :                                 OOLog(@"joystick.connect.element", @"%@ - usage %d:%d, cookie %d, range %d-%d", name, usagePage, usage, (int) elementCookie, min, max);
     163             :                         }
     164             :                         
     165             :                         OOLogOutdent();
     166             :                         CFRelease(elementList);
     167             :                 }
     168             :         }
     169             : }
     170             : 
     171             : 
     172           0 : - (void) handleDeviceRemoval:(IOHIDDeviceRef)device
     173             : {
     174             :         OOLog(@"joystick.remove", @"Joystick removed: %@", IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductKey)));
     175             :         
     176             :         CFIndex index = CFArrayGetFirstIndexOfValue(devices, CFRangeMake(0, CFArrayGetCount(devices)), device);
     177             :         if (index != kCFNotFound)  CFArrayRemoveValueAtIndex(devices, index);
     178             : }
     179             : 
     180             : 
     181           0 : static int AxisIndex(uint32_t page, uint32_t usage)
     182             : {
     183             :         /*
     184             :                 Map axis-like HID usages to SDL-like axis indices. These are all the
     185             :                 commonly used joystick axes according to Microsoft's DirectInput
     186             :                 documentation (hey, you've got to get your info somewhere).
     187             :                 
     188             :                 GD_Slider, GD_Dial, DG_Wheel and Sim_Throttle are actually distinct;
     189             :                 unlike the others, they're uncentered. (By implication,
     190             :                 IOHIDElementHasPreferredState() should be false.) This should, in
     191             :                 particular, be considered when mapping to the throttle: centered axes
     192             :                 should provide relative input (for gamepads), while uncentered ones
     193             :                 should provide absolute input. Since that festering pool of pus, SDL,
     194             :                 can't make this distinction, OOJoystickManager can't either (yet).
     195             :                 -- Ahruman 2011-01-04
     196             :         */
     197             :         
     198             :         switch (page)
     199             :         {
     200             :                 case kHIDPage_GenericDesktop:
     201             :                         switch (usage)
     202             :                         {
     203             :                                 case kHIDUsage_GD_X: return 0;
     204             :                                 case kHIDUsage_GD_Y: return 1;
     205             :                                 case kHIDUsage_GD_Z: return 2;
     206             :                                 case kHIDUsage_GD_Rx: return 3;
     207             :                                 case kHIDUsage_GD_Ry: return 4;
     208             :                                 case kHIDUsage_GD_Rz: return 5;
     209             :                                 case kHIDUsage_GD_Slider: return 6;
     210             :                                 case kHIDUsage_GD_Dial: return 7;
     211             :                                 case kHIDUsage_GD_Wheel: return 8;
     212             :                         }
     213             :                         break;
     214             :                 
     215             :                 case kHIDPage_Simulation:
     216             :                         switch (usage)
     217             :                 {
     218             :                         case kHIDUsage_Sim_Throttle:
     219             :                                 return 9;
     220             :                 }
     221             :         }
     222             :         
     223             :         // Negative numbers indicate non-axis.
     224             :         return -1;
     225             : }
     226             : 
     227             : 
     228           0 : static uint8_t MapHatValue(CFIndex value, CFIndex max)
     229             : {
     230             :         /*
     231             :                 A hat switch has four or eight values, indicating directions. 0
     232             :                 is straight up/forwards, and subsequent values increase clockwise.
     233             :                 Out-of-range values are nulls, indicating no direction is pressed.
     234             :         */
     235             :         
     236             :         if (0 <= value && value <= max)
     237             :         {
     238             :                 if (max == 3)
     239             :                 {
     240             :                         const uint8_t valueMap4[4] = { JOYHAT_UP, JOYHAT_RIGHT, JOYHAT_DOWN, JOYHAT_LEFT };
     241             :                         return valueMap4[value];
     242             :                 }
     243             :                 else if (max == 7)
     244             :                 {
     245             :                         const uint8_t valueMap8[8] = { JOYHAT_UP, JOYHAT_RIGHTUP, JOYHAT_RIGHT, JOYHAT_RIGHTDOWN, JOYHAT_DOWN, JOYHAT_LEFTDOWN, JOYHAT_LEFT, JOYHAT_LEFTUP };
     246             :                         return valueMap8[value];
     247             :                 }
     248             :         }
     249             :         return JOYHAT_CENTERED;
     250             : }
     251             : 
     252             : 
     253           0 : - (void) handleInputEvent:(IOHIDValueRef)value
     254             : {
     255             :         IOHIDElementRef element = IOHIDValueGetElement(value);
     256             :         uint32_t usagePage = IOHIDElementGetUsagePage(element);
     257             :         uint32_t usage = IOHIDElementGetUsage(element);
     258             :         int buttonNum = 0;
     259             :         int axisNum = 0;
     260             :         
     261             :         axisNum = AxisIndex(usagePage, usage);
     262             :         if (axisNum >= 0)
     263             :         {
     264             :                 JoyAxisEvent evt;
     265             :                 evt.type = JOYAXISMOTION;
     266             :                 evt.which = 0;
     267             :                 evt.axis = axisNum;
     268             :                 
     269             :                 CFIndex intValue = IOHIDValueGetIntegerValue(value);
     270             :                 CFIndex min = IOHIDElementGetLogicalMin(element);
     271             :                 CFIndex max = IOHIDElementGetLogicalMax(element);
     272             :                 float axisValue = (float)(intValue - min) / (float)(max + 1 - min);
     273             :                 
     274             :                 // Note: this is designed so gammaIndex == intValue if min == 0 and max == kJoystickGammaTableSize - 1 (the common case).
     275             :                 unsigned gammaIndex = floor(axisValue * kJoystickGammaTableSize);
     276             :                 
     277             :                 /*
     278             :                         CRASH: r4435, Mac OS X 10.6.6, x86_64 -[OOMacJoystickManager handleInputEvent:] + 260
     279             :                         http://aegidian.org/bb/viewtopic.php?f=3&t=9382
     280             :                         The instruction is:
     281             :                                 movl (%r15,%rbx,4), %r12d
     282             :                         which corresponds to the load gammaTable[gammaIndex].
     283             :                         Check added to find out-of-range values.
     284             :                         -- Ahruman 2011-03-07
     285             :                 */
     286             :                 if (EXPECT_NOT(gammaIndex > kJoystickGammaTableSize))
     287             :                 {
     288             :                         OOLogERR(@"joystick.gamma.overflow", @"Joystick gamma table overflow - gammaIndex is %u of %u. Raw value: %i in [%i..%i]; axisValue: %g; unrounded gammaIndex: %g. Ignoring event. This is an internal error, please report it.",
     289             :                                          gammaIndex, kJoystickGammaTableSize,
     290             :                                          (int)intValue, (int)min, (int)max,
     291             :                                          axisValue,
     292             :                                          axisValue * kJoystickGammaTableSize
     293             :                         );
     294             :                         
     295             :                         return;
     296             :                 }
     297             :                 // End bug check
     298             :                 
     299             :                 evt.value = gammaTable[gammaIndex];
     300             :                 [self decodeAxisEvent:&evt];
     301             :         }
     302             :         else if (usagePage == kHIDPage_GenericDesktop && usage == kHIDUsage_GD_Hatswitch)
     303             :         {
     304             :                 CFIndex max = IOHIDElementGetLogicalMax(element);
     305             :                 CFIndex min = IOHIDElementGetLogicalMin(element);
     306             :                 
     307             :                 JoyHatEvent evt =
     308             :                 {
     309             :                         .type = JOYHAT_MOTION,
     310             :                         .which = 0,
     311             :                         .hat = 0,       // The abuse of usage values for identifying elements means we can't distinguish between hats.
     312             :                         .value = MapHatValue(IOHIDValueGetIntegerValue(value) - min, max - min)
     313             :                 };
     314             :                 
     315             :                 [self decodeHatEvent:&evt];
     316             :         }
     317             :         else if (usagePage == kHIDPage_Button)
     318             :         {
     319             :                 // Button Event
     320             :                 buttonNum = usage;
     321             :                 JoyButtonEvent evt;
     322             :                 BOOL buttonState = (IOHIDValueGetIntegerValue(value) != 0);
     323             :                 evt.type = buttonState ? JOYBUTTONDOWN : JOYBUTTONUP;
     324             :                 evt.which = 0;
     325             :                 evt.button = buttonNum;
     326             :                 evt.state = buttonState ? 1 : 0;        
     327             :                 [self decodeButtonEvent:&evt];
     328             :         }
     329             : }
     330             : 
     331             : 
     332           0 : - (NSString *) nameOfJoystick:(NSUInteger)stickNumber
     333             : {
     334             :         IOHIDDeviceRef device = (IOHIDDeviceRef)CFArrayGetValueAtIndex(devices, stickNumber);
     335             :         return (NSString *)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductKey));
     336             : }
     337             : 
     338             : 
     339             : 
     340           0 : - (int16_t) getAxisWithStick:(NSUInteger) stickNum axis:(NSUInteger) axisNum 
     341             : {
     342             :         return 0;
     343             : }
     344             : 
     345             : 
     346             : 
     347             : @end
     348             : 
     349             : 
     350             : //Thunking to Objective-C
     351           0 : static void HandleDeviceMatchingCallback(void * inContext, IOReturn inResult, void* inSender, IOHIDDeviceRef  inIOHIDDeviceRef)
     352             : {
     353             :         [(OOMacJoystickManager *)inContext handleDeviceAttach:inIOHIDDeviceRef];
     354             : }
     355             : 
     356             : 
     357             : 
     358           0 : static void HandleInputValueCallback(void * inContext, IOReturn inResult, void* inSender, IOHIDValueRef  inIOHIDValueRef)
     359             : {
     360             :         [(OOMacJoystickManager *)inContext handleInputEvent:inIOHIDValueRef];
     361             : }
     362             : 
     363             : 
     364             : 
     365           0 : static void HandleDeviceRemovalCallback(void * inContext, IOReturn inResult, void* inSender, IOHIDDeviceRef  inIOHIDDeviceRef)
     366             : {
     367             :         [(OOMacJoystickManager *)inContext handleDeviceRemoval:inIOHIDDeviceRef];
     368             : }

Generated by: LCOV version 1.14