BsMacOSInput.cpp 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894
  1. //********************************** Banshee Engine (www.banshee3d.com) **************************************************//
  2. //**************** Copyright (c) 2016 Marko Pintera ([email protected]). All rights reserved. **********************//
  3. #include "Input/BsInput.h"
  4. #include "Private/MacOS/BsMacOSInput.h"
  5. #include "Input/BsMouse.h"
  6. #include "Input/BsKeyboard.h"
  7. #include "Input/BsGamepad.h"
  8. namespace bs
  9. {
  10. /**
  11. * Helper method that creates a dictionary that is used for matching a specific set of devices (matching the provided
  12. * page and usage values, as USB HID values), used for initializing a HIDManager.
  13. */
  14. static CFDictionaryRef createHIDDeviceMatchDictionary(UINT32 page, UINT32 usage)
  15. {
  16. CFDictionaryRef output = nullptr;
  17. CFNumberRef pageNumRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &page);
  18. CFNumberRef usageNumRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &usage);
  19. const void* keys[2] = { (void*) CFSTR(kIOHIDDeviceUsagePageKey), (void*) CFSTR(kIOHIDDeviceUsageKey) };
  20. const void* values[2] = { (void*) pageNumRef, (void*) usageNumRef };
  21. if (pageNumRef && usageNumRef)
  22. {
  23. output = CFDictionaryCreate(kCFAllocatorDefault, keys, values, 2, &kCFTypeDictionaryKeyCallBacks,
  24. &kCFTypeDictionaryValueCallBacks);
  25. }
  26. if (pageNumRef)
  27. CFRelease(pageNumRef);
  28. if (usageNumRef)
  29. CFRelease(usageNumRef);
  30. return output;
  31. }
  32. /** Returns the name of the run loop used for processing events for the specified category of input devices. */
  33. static CFStringRef getRunLoopMode(HIDType type)
  34. {
  35. static CFStringRef KeyboardMode = CFSTR("BSKeyboard");
  36. static CFStringRef MouseMode = CFSTR("BSMouse");
  37. static CFStringRef GamepadMode = CFSTR("BSGamepad");
  38. switch(type)
  39. {
  40. case HIDType::Keyboard:
  41. return KeyboardMode;
  42. case HIDType::Mouse:
  43. return MouseMode;
  44. case HIDType::Gamepad:
  45. return GamepadMode;
  46. }
  47. return nullptr;
  48. }
  49. static void HIDAddElements(CFArrayRef array, HIDDevice* device);
  50. /**
  51. * Callback called when enumerating an array of HID elements. Each element's information is parsed and stored in the
  52. * owner HIDDevice (passed through @p passthrough parameter).
  53. *
  54. * @param[in] value IOHIDElementRef of the current element.
  55. * @param[in] passthrough Pointer to element's parent HIDDevice.
  56. */
  57. static void HIDAddElement(const void* value, void* passthrough)
  58. {
  59. auto device = (HIDDevice*)passthrough;
  60. auto elemRef = (IOHIDElementRef) value;
  61. if(!elemRef)
  62. return;
  63. CFTypeID typeID = CFGetTypeID(elemRef);
  64. if(typeID != IOHIDElementGetTypeID())
  65. return;
  66. IOHIDElementType type = IOHIDElementGetType(elemRef);
  67. switch (type)
  68. {
  69. case kIOHIDElementTypeInput_Button:
  70. case kIOHIDElementTypeInput_Axis:
  71. case kIOHIDElementTypeInput_Misc:
  72. case kIOHIDElementTypeInput_ScanCodes:
  73. break;
  74. case kIOHIDElementTypeCollection:
  75. {
  76. CFArrayRef array = IOHIDElementGetChildren(elemRef);
  77. if(array)
  78. HIDAddElements(array, device);
  79. }
  80. return;
  81. default:
  82. return;
  83. }
  84. UINT32 usagePage = IOHIDElementGetUsagePage(elemRef);
  85. UINT32 usage = IOHIDElementGetUsage(elemRef);
  86. enum ElemState { IsUnknown, IsButton, IsAxis, IsHat };
  87. ElemState state = IsUnknown;
  88. switch(usagePage)
  89. {
  90. case kHIDPage_Button:
  91. case kHIDPage_KeyboardOrKeypad:
  92. state = IsButton;
  93. break;
  94. case kHIDPage_GenericDesktop:
  95. switch(usage)
  96. {
  97. case kHIDUsage_GD_Start:
  98. case kHIDUsage_GD_Select:
  99. case kHIDUsage_GD_SystemMainMenu:
  100. case kHIDUsage_GD_DPadUp:
  101. case kHIDUsage_GD_DPadDown:
  102. case kHIDUsage_GD_DPadRight:
  103. case kHIDUsage_GD_DPadLeft:
  104. state = IsButton;
  105. break;
  106. case kHIDUsage_GD_X:
  107. case kHIDUsage_GD_Y:
  108. case kHIDUsage_GD_Z:
  109. case kHIDUsage_GD_Rx:
  110. case kHIDUsage_GD_Ry:
  111. case kHIDUsage_GD_Rz:
  112. case kHIDUsage_GD_Slider:
  113. case kHIDUsage_GD_Dial:
  114. case kHIDUsage_GD_Wheel:
  115. state = IsAxis;
  116. break;
  117. case kHIDUsage_GD_Hatswitch:
  118. state = IsHat;
  119. break;
  120. default:
  121. break;
  122. };
  123. break;
  124. case kHIDPage_Simulation:
  125. switch(usage)
  126. {
  127. case kHIDUsage_Sim_Rudder:
  128. case kHIDUsage_Sim_Throttle:
  129. case kHIDUsage_Sim_Accelerator:
  130. case kHIDUsage_Sim_Brake:
  131. state = IsAxis;
  132. default:
  133. break;
  134. }
  135. break;
  136. default:
  137. break;
  138. };
  139. Vector<HIDElement>* elements = nullptr;
  140. switch(state)
  141. {
  142. case IsButton:
  143. elements = &device->buttons;
  144. break;
  145. case IsAxis:
  146. elements = &device->axes;
  147. break;
  148. case IsHat:
  149. elements = &device->hats;
  150. break;
  151. default:
  152. break;
  153. }
  154. if(elements != nullptr)
  155. {
  156. HIDElement element;
  157. element.usage = usage;
  158. element.ref = elemRef;
  159. element.cookie = IOHIDElementGetCookie(elemRef);
  160. element.min = element.detectedMin = (INT32)IOHIDElementGetLogicalMin(elemRef);
  161. element.max = element.detectedMax = (INT32)IOHIDElementGetLogicalMax(elemRef);
  162. auto iterFind = std::find_if(elements->begin(), elements->end(),
  163. [&element](const HIDElement& v)
  164. {
  165. return v.cookie == element.cookie;
  166. });
  167. if(iterFind == elements->end())
  168. elements->push_back(element);
  169. }
  170. }
  171. /** Parses information about and registers all HID elements in @p array with the @p device. */
  172. void HIDAddElements(CFArrayRef array, HIDDevice* device)
  173. {
  174. CFRange range = { 0, CFArrayGetCount(array) };
  175. CFArrayApplyFunction(array, range, HIDAddElement, device);
  176. }
  177. /**
  178. * Callback triggered when a HID manager detects a new device. Also called for existing devices when HID manager is
  179. * first initialized.
  180. */
  181. static void HIDDeviceAddedCallback(void* context, IOReturn result, void* sender, IOHIDDeviceRef device)
  182. {
  183. auto data = (HIDData*)context;
  184. for(auto& entry : data->devices)
  185. {
  186. if(entry.ref == device)
  187. return; // Duplicate
  188. }
  189. HIDDevice newDevice;
  190. newDevice.ref = device;
  191. bs_zero_out(newDevice.gamepadAxisTimestamps);
  192. // Parse device name
  193. CFTypeRef propertyRef = IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductKey));
  194. if(!propertyRef)
  195. propertyRef = IOHIDDeviceGetProperty(device, CFSTR(kIOHIDManufacturerKey));
  196. if(propertyRef)
  197. {
  198. char buffer[256];
  199. if(CFStringGetCString((CFStringRef)propertyRef, buffer, sizeof(buffer), kCFStringEncodingUTF8))
  200. newDevice.name = String(buffer);
  201. }
  202. // Parse device elements
  203. CFArrayRef elements = IOHIDDeviceCopyMatchingElements(device, nullptr, kIOHIDOptionsTypeNone);
  204. if(elements)
  205. {
  206. HIDAddElements(elements, &newDevice);
  207. CFRelease(elements);
  208. }
  209. // Create a queue
  210. newDevice.queueRef = IOHIDQueueCreate(kCFAllocatorDefault, device, 128, kIOHIDOptionsTypeNone);
  211. for(auto& button : newDevice.buttons)
  212. IOHIDQueueAddElement(newDevice.queueRef, button.ref);
  213. for(auto& hat : newDevice.hats)
  214. IOHIDQueueAddElement(newDevice.queueRef, hat.ref);
  215. IOHIDQueueStart(newDevice.queueRef);
  216. // Assign a device ID
  217. if(data->type == HIDType::Gamepad)
  218. {
  219. auto freeId = (UINT32)-1;
  220. auto numDevices = (UINT32)data->devices.size();
  221. for(UINT32 i = 0; i < numDevices; i++)
  222. {
  223. bool validId = true;
  224. for(auto& entry : data->devices)
  225. {
  226. if(entry.id == i)
  227. {
  228. validId = false;
  229. break;
  230. }
  231. }
  232. if(validId)
  233. {
  234. freeId = i;
  235. break;
  236. }
  237. }
  238. if(freeId == (UINT32)-1)
  239. freeId = numDevices;
  240. newDevice.id = freeId;
  241. }
  242. else // All keyboard/mouse devices are coalesced into a single device
  243. newDevice.id = 0;
  244. data->devices.push_back(newDevice);
  245. // Register the gamepad device with Input manager
  246. if(data->type == HIDType::Gamepad)
  247. {
  248. InputPrivateData* pvtData = data->owner->_getPrivateData();
  249. GamepadInfo gamepadInfo;
  250. gamepadInfo.name = newDevice.name;
  251. gamepadInfo.id = newDevice.id;
  252. gamepadInfo.deviceRef = newDevice.ref;
  253. gamepadInfo.hid = nullptr;
  254. pvtData->gamepadInfos.push_back(gamepadInfo);
  255. }
  256. }
  257. /** Callback triggered when an input device is removed. */
  258. static void HIDDeviceRemovedCallback(void* context, IOReturn result, void* sender, IOHIDDeviceRef device)
  259. {
  260. auto data = (HIDData*)context;
  261. auto iterFind = std::find_if(data->devices.begin(), data->devices.end(),
  262. [&device](const HIDDevice& v)
  263. {
  264. return v.ref == device;
  265. });
  266. if(iterFind != data->devices.end())
  267. {
  268. IOHIDQueueStop(iterFind->queueRef);
  269. CFRelease(iterFind->queueRef);
  270. // Unregister the gamepad device from the Input manager
  271. if(data->type == HIDType::Gamepad)
  272. {
  273. InputPrivateData* pvtData = data->owner->_getPrivateData();
  274. UINT32 deviceId = iterFind->id;
  275. auto iterFind2 = std::find_if(
  276. pvtData->gamepadInfos.begin(),
  277. pvtData->gamepadInfos.end(),
  278. [deviceId](const GamepadInfo& info)
  279. {
  280. return info.id == deviceId;
  281. });
  282. if(iterFind2 != pvtData->gamepadInfos.end())
  283. pvtData->gamepadInfos.erase(iterFind2);
  284. }
  285. data->devices.erase(iterFind);
  286. }
  287. }
  288. /** Reads the current value of a particular HID element (e.g. button, axis). */
  289. static INT32 HIDGetElementValue(const HIDDevice &device, const HIDElement &element)
  290. {
  291. IOHIDValueRef valueRef;
  292. if(IOHIDDeviceGetValue(device.ref, element.ref, &valueRef) != kIOReturnSuccess)
  293. return 0;
  294. auto value = (INT32) IOHIDValueGetIntegerValue(valueRef);
  295. if(value < element.detectedMin)
  296. element.detectedMin = value;
  297. if(value > element.detectedMax)
  298. element.detectedMax = value;
  299. return value;
  300. }
  301. /**
  302. * Reads the current value of a particular HID element (e.g. button, axis) and converts the value so it fits
  303. * the provided [min, max] range.
  304. */
  305. static INT32 HIDGetElementValueScaled(const HIDDevice &device, const HIDElement &element, INT32 min, INT32 max)
  306. {
  307. INT32 value = HIDGetElementValue(device, element);
  308. float deviceRange = element.detectedMax - element.detectedMin;
  309. if(deviceRange == 0.0f)
  310. return value;
  311. float normalizedRange = (value - element.detectedMin) / deviceRange;
  312. float targetRange = max - min;
  313. return (INT32)(normalizedRange * targetRange) + min;
  314. }
  315. /** Callback triggered when an input value changes. */
  316. static void HIDValueChangedCallback(void* context, IOReturn result, void* sender, IOHIDValueRef valueRef)
  317. {
  318. auto data = (HIDData*)context;
  319. IOHIDElementRef elementRef = IOHIDValueGetElement(valueRef);
  320. auto usage = (UINT32) IOHIDElementGetUsage(elementRef);
  321. auto axisValue = (INT32)IOHIDValueGetIntegerValue(valueRef);
  322. switch (usage)
  323. {
  324. case kHIDUsage_GD_X:
  325. data->mouseAxisValues[0] += axisValue;
  326. break;
  327. case kHIDUsage_GD_Y:
  328. data->mouseAxisValues[1] += axisValue;
  329. break;
  330. case kHIDUsage_GD_Z:
  331. data->mouseAxisValues[2] += axisValue;
  332. break;
  333. default:
  334. break;
  335. }
  336. }
  337. /** Converts a keyboard scan key (as reported by the HID manager) into engine's ButtonCode. */
  338. static ButtonCode scanCodeToKeyCode(UINT32 scanCode)
  339. {
  340. switch(scanCode)
  341. {
  342. case 0x04: return BC_A;
  343. case 0x05: return BC_B;
  344. case 0x06: return BC_C;
  345. case 0x07: return BC_D;
  346. case 0x08: return BC_E;
  347. case 0x09: return BC_F;
  348. case 0x0a: return BC_G;
  349. case 0x0b: return BC_H;
  350. case 0x0c: return BC_I;
  351. case 0x0d: return BC_J;
  352. case 0x0e: return BC_K;
  353. case 0x0f: return BC_L;
  354. case 0x10: return BC_M;
  355. case 0x11: return BC_N;
  356. case 0x12: return BC_O;
  357. case 0x13: return BC_P;
  358. case 0x14: return BC_Q;
  359. case 0x15: return BC_R;
  360. case 0x16: return BC_S;
  361. case 0x17: return BC_T;
  362. case 0x18: return BC_U;
  363. case 0x19: return BC_V;
  364. case 0x1a: return BC_W;
  365. case 0x1b: return BC_X;
  366. case 0x1c: return BC_Y;
  367. case 0x1d: return BC_Z;
  368. case 0x1e: return BC_1;
  369. case 0x1f: return BC_2;
  370. case 0x20: return BC_3;
  371. case 0x21: return BC_4;
  372. case 0x22: return BC_5;
  373. case 0x23: return BC_6;
  374. case 0x24: return BC_7;
  375. case 0x25: return BC_8;
  376. case 0x26: return BC_9;
  377. case 0x27: return BC_0;
  378. case 0x28: return BC_RETURN;
  379. case 0x29: return BC_ESCAPE;
  380. case 0x2a: return BC_BACK;
  381. case 0x2b: return BC_TAB;
  382. case 0x2c: return BC_SPACE;
  383. case 0x2d: return BC_MINUS;
  384. case 0x2e: return BC_EQUALS;
  385. case 0x2f: return BC_LBRACKET;
  386. case 0x30: return BC_RBRACKET;
  387. case 0x31: return BC_BACKSLASH;
  388. case 0x32: return BC_GRAVE;
  389. case 0x33: return BC_SEMICOLON;
  390. case 0x34: return BC_APOSTROPHE;
  391. case 0x35: return BC_GRAVE;
  392. case 0x36: return BC_COMMA;
  393. case 0x37: return BC_PERIOD;
  394. case 0x38: return BC_SLASH;
  395. case 0x39: return BC_CAPITAL;
  396. case 0x3a: return BC_F1;
  397. case 0x3b: return BC_F2;
  398. case 0x3c: return BC_F3;
  399. case 0x3d: return BC_F4;
  400. case 0x3e: return BC_F5;
  401. case 0x3f: return BC_F6;
  402. case 0x40: return BC_F7;
  403. case 0x41: return BC_F8;
  404. case 0x42: return BC_F9;
  405. case 0x43: return BC_F10;
  406. case 0x44: return BC_F11;
  407. case 0x45: return BC_F12;
  408. case 0x46: return BC_SYSRQ;
  409. case 0x47: return BC_SCROLL;
  410. case 0x48: return BC_PAUSE;
  411. case 0x49: return BC_INSERT;
  412. case 0x4a: return BC_HOME;
  413. case 0x4b: return BC_PGUP;
  414. case 0x4c: return BC_DELETE;
  415. case 0x4d: return BC_END;
  416. case 0x4e: return BC_PGDOWN;
  417. case 0x4f: return BC_RIGHT;
  418. case 0x50: return BC_LEFT;
  419. case 0x51: return BC_DOWN;
  420. case 0x52: return BC_UP;
  421. case 0x53: return BC_NUMLOCK;
  422. case 0x54: return BC_DIVIDE;
  423. case 0x55: return BC_MULTIPLY;
  424. case 0x56: return BC_SUBTRACT;
  425. case 0x57: return BC_ADD;
  426. case 0x58: return BC_NUMPADENTER;
  427. case 0x59: return BC_NUMPAD1;
  428. case 0x5a: return BC_NUMPAD2;
  429. case 0x5b: return BC_NUMPAD3;
  430. case 0x5c: return BC_NUMPAD4;
  431. case 0x5d: return BC_NUMPAD5;
  432. case 0x5e: return BC_NUMPAD6;
  433. case 0x5f: return BC_NUMPAD7;
  434. case 0x60: return BC_NUMPAD8;
  435. case 0x61: return BC_NUMPAD9;
  436. case 0x62: return BC_NUMPAD0;
  437. case 0x63: return BC_NUMPADCOMMA;
  438. case 0x64: return BC_OEM_102;
  439. case 0x66: return BC_POWER;
  440. case 0x67: return BC_NUMPADEQUALS;
  441. case 0x68: return BC_F13;
  442. case 0x69: return BC_F14;
  443. case 0x6a: return BC_F15;
  444. case 0x78: return BC_STOP;
  445. case 0x7f: return BC_MUTE;
  446. case 0x80: return BC_VOLUMEUP;
  447. case 0x81: return BC_VOLUMEDOWN;
  448. case 0x85: return BC_NUMPADCOMMA;
  449. case 0x86: return BC_NUMPADEQUALS;
  450. case 0x89: return BC_YEN;
  451. case 0xe0: return BC_LCONTROL;
  452. case 0xe1: return BC_LSHIFT;
  453. case 0xe2: return BC_LMENU;
  454. case 0xe3: return BC_LWIN;
  455. case 0xe4: return BC_RCONTROL;
  456. case 0xe5: return BC_RSHIFT;
  457. case 0xe6: return BC_RMENU;
  458. case 0xe7: return BC_RWIN;
  459. case 0xe8: return BC_PLAYPAUSE;
  460. case 0xe9: return BC_MEDIASTOP;
  461. case 0xea: return BC_PREVTRACK;
  462. case 0xeb: return BC_NEXTTRACK;
  463. case 0xed: return BC_VOLUMEUP;
  464. case 0xee: return BC_VOLUMEDOWN;
  465. case 0xef: return BC_MUTE;
  466. case 0xf0: return BC_WEBSEARCH;
  467. case 0xf1: return BC_WEBBACK;
  468. case 0xf2: return BC_WEBFORWARD;
  469. case 0xf3: return BC_WEBSTOP;
  470. case 0xf4: return BC_WEBSEARCH;
  471. case 0xf8: return BC_SLEEP;
  472. case 0xf9: return BC_WAKE;
  473. case 0xfb: return BC_CALCULATOR;
  474. default:
  475. return BC_UNASSIGNED;
  476. }
  477. }
  478. HIDManager::HIDManager(HIDType type, Input* input)
  479. {
  480. mData.type = type;
  481. mData.owner = input;
  482. bs_zero_out(mData.mouseAxisValues);
  483. mHIDManager = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDManagerOptionNone);
  484. if(mHIDManager == nullptr)
  485. return;
  486. if(IOHIDManagerOpen(mHIDManager, kIOHIDOptionsTypeNone) != kIOReturnSuccess)
  487. return;
  488. UINT32 numEntries = 0;
  489. const void* entries[3];
  490. switch (type)
  491. {
  492. case HIDType::Keyboard:
  493. entries[0] = createHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_Keyboard);
  494. numEntries = 1;
  495. break;
  496. case HIDType::Mouse:
  497. entries[0] = createHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_Mouse);
  498. numEntries = 1;
  499. break;
  500. case HIDType::Gamepad:
  501. entries[0] = createHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_Joystick);
  502. entries[1] = createHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_GamePad);
  503. entries[2] = createHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_MultiAxisController);
  504. numEntries = 3;
  505. break;
  506. }
  507. CFArrayRef entryArray = CFArrayCreate(kCFAllocatorDefault, entries, numEntries, &kCFTypeArrayCallBacks);
  508. IOHIDManagerSetDeviceMatchingMultiple(mHIDManager, entryArray);
  509. IOHIDManagerRegisterDeviceMatchingCallback(mHIDManager, HIDDeviceAddedCallback, &mData);
  510. IOHIDManagerRegisterDeviceRemovalCallback(mHIDManager, HIDDeviceRemovedCallback, &mData);
  511. // We only care about input callbacks for mice, so we can accumulate all axis movement
  512. if(type == HIDType::Mouse)
  513. IOHIDManagerRegisterInputValueCallback(mHIDManager, HIDValueChangedCallback, &mData);
  514. CFStringRef runLoopMode = getRunLoopMode(type);
  515. IOHIDManagerScheduleWithRunLoop(mHIDManager, CFRunLoopGetCurrent(), runLoopMode);
  516. while(CFRunLoopRunInMode(runLoopMode, 0, TRUE) == kCFRunLoopRunHandledSource)
  517. { /* Do nothing */ }
  518. for (UINT32 i = 0; i < numEntries; i++)
  519. {
  520. if (entries[i])
  521. CFRelease((CFTypeRef) entries[i]);
  522. }
  523. CFRelease(entryArray);
  524. }
  525. HIDManager::~HIDManager()
  526. {
  527. for(auto& device : mData.devices)
  528. {
  529. IOHIDQueueStop(device.queueRef);
  530. CFRelease(device.queueRef);
  531. }
  532. CFStringRef runLoopMode = getRunLoopMode(mData.type);
  533. IOHIDManagerUnscheduleFromRunLoop(mHIDManager, CFRunLoopGetCurrent(), runLoopMode);
  534. IOHIDManagerClose(mHIDManager, kIOHIDOptionsTypeNone);
  535. CFRelease(mHIDManager);
  536. }
  537. void HIDManager::capture(IOHIDDeviceRef device, bool ignoreEvents)
  538. {
  539. if(mData.type == HIDType::Mouse)
  540. bs_zero_out(mData.mouseAxisValues);
  541. // First trigger any callbacks
  542. CFStringRef runLoopMode = getRunLoopMode(mData.type);
  543. while(CFRunLoopRunInMode(runLoopMode, 0, TRUE) == kCFRunLoopRunHandledSource)
  544. { /* Do nothing */ }
  545. for(auto& entry : mData.devices)
  546. {
  547. if(device != nullptr && entry.ref != device)
  548. continue;
  549. // Read non-queued elements
  550. // These are generally gamepad axes for which we only care about the latest absolute values
  551. if(!ignoreEvents)
  552. {
  553. struct AxisState
  554. {
  555. bool moved;
  556. INT32 value;
  557. };
  558. AxisState axisValues[HID_NUM_GAMEPAD_AXES];
  559. bs_zero_out(axisValues);
  560. for (auto& axis : entry.axes)
  561. {
  562. auto axisType = (InputAxis) -1;
  563. if (mData.type == HIDType::Gamepad)
  564. {
  565. INT32 axisValue = HIDGetElementValueScaled(entry, axis, Gamepad::MIN_AXIS, Gamepad::MAX_AXIS);
  566. INT32 lastInputAxis = (INT32) InputAxis::RightTrigger + 1;
  567. switch (axis.usage)
  568. {
  569. case kHIDUsage_GD_X:
  570. axisType = InputAxis::LeftStickX;
  571. break;
  572. case kHIDUsage_GD_Y:
  573. axisType = InputAxis::LeftStickY;
  574. break;
  575. case kHIDUsage_GD_Rx:
  576. axisType = InputAxis::RightStickX;
  577. break;
  578. case kHIDUsage_GD_Ry:
  579. axisType = InputAxis::RightStickY;
  580. break;
  581. case kHIDUsage_GD_Z:
  582. axisType = InputAxis::LeftTrigger;
  583. break;
  584. case kHIDUsage_GD_Rz:
  585. axisType = InputAxis::RightTrigger;
  586. break;
  587. case kHIDUsage_GD_Slider:
  588. axisType = (InputAxis) (lastInputAxis + 1);
  589. break;
  590. case kHIDUsage_GD_Dial:
  591. axisType = (InputAxis) (lastInputAxis + 2);
  592. break;
  593. case kHIDUsage_GD_Wheel:
  594. axisType = (InputAxis) (lastInputAxis + 3);
  595. break;
  596. case kHIDUsage_Sim_Rudder:
  597. axisType = (InputAxis) (lastInputAxis + 4);
  598. break;
  599. case kHIDUsage_Sim_Throttle:
  600. axisType = (InputAxis) (lastInputAxis + 5);
  601. break;
  602. case kHIDUsage_Sim_Accelerator:
  603. axisType = (InputAxis) (lastInputAxis + 6);
  604. break;
  605. case kHIDUsage_Sim_Brake:
  606. axisType = (InputAxis) (lastInputAxis + 7);
  607. break;
  608. default:
  609. break;
  610. }
  611. if((INT32)axisType < HID_NUM_GAMEPAD_AXES)
  612. {
  613. IOHIDValueRef valueRef;
  614. if(IOHIDDeviceGetValue(device, axis.ref, &valueRef) != kIOReturnSuccess)
  615. continue;
  616. // Ignore if axis value didn't change since last query
  617. UINT64 timestamp = IOHIDValueGetTimeStamp(valueRef);
  618. if(timestamp == entry.gamepadAxisTimestamps[(INT32)axisType])
  619. continue;
  620. axisValues[(INT32)axisType].moved = true;
  621. axisValues[(INT32)axisType].value = axisValue;
  622. entry.gamepadAxisTimestamps[(INT32)axisType] = timestamp;
  623. }
  624. }
  625. }
  626. for(UINT32 i = 0; i < HID_NUM_GAMEPAD_AXES; i++)
  627. {
  628. if(axisValues[i].moved)
  629. mData.owner->_notifyAxisMoved(entry.id, (UINT32)i, axisValues[i].value);
  630. }
  631. }
  632. // Read queued elements
  633. while(true)
  634. {
  635. IOHIDValueRef valueRef = IOHIDQueueCopyNextValueWithTimeout(entry.queueRef, 0);
  636. if(!valueRef)
  637. break;
  638. if(ignoreEvents)
  639. continue;
  640. IOHIDElementRef elemRef = IOHIDValueGetElement(valueRef);
  641. auto value = (INT32) IOHIDValueGetIntegerValue(valueRef); // For buttons this is 1 when pressed, 0 when released
  642. UINT64 timestamp = IOHIDValueGetTimeStamp(valueRef);
  643. UINT32 usage = IOHIDElementGetUsage(elemRef);
  644. UINT32 usagePage = IOHIDElementGetUsagePage(elemRef);
  645. ButtonCode button = BC_UNASSIGNED;
  646. if(usagePage == kHIDPage_GenericDesktop)
  647. {
  648. if (usage == kHIDUsage_GD_Hatswitch)
  649. {
  650. switch (value)
  651. {
  652. case 0:
  653. button = BC_GAMEPAD_DPAD_UP;
  654. break;
  655. case 1:
  656. button = BC_GAMEPAD_DPAD_UPRIGHT;
  657. break;
  658. case 2:
  659. button = BC_GAMEPAD_DPAD_RIGHT;
  660. break;
  661. case 3:
  662. button = BC_GAMEPAD_DPAD_DOWNRIGHT;
  663. break;
  664. case 4:
  665. button = BC_GAMEPAD_DPAD_DOWN;
  666. break;
  667. case 5:
  668. button = BC_GAMEPAD_DPAD_DOWNLEFT;
  669. break;
  670. case 6:
  671. button = BC_GAMEPAD_DPAD_LEFT;
  672. break;
  673. case 7:
  674. button = BC_GAMEPAD_DPAD_UPLEFT;
  675. break;
  676. default:
  677. break;
  678. }
  679. }
  680. }
  681. else if(usagePage == kHIDPage_Button)
  682. {
  683. if(mData.type == HIDType::Mouse)
  684. {
  685. if (usage > 0 && usage <= BC_NumMouse)
  686. button = (ButtonCode) ((UINT32) BC_MOUSE_LEFT + usage - 1);
  687. }
  688. else if(mData.type == HIDType::Gamepad)
  689. {
  690. // These are based on the xbox controller:
  691. switch(usage)
  692. {
  693. case 0: break;
  694. case 1: button = BC_GAMEPAD_A; break;
  695. case 2: button = BC_GAMEPAD_B; break;
  696. case 3: button = BC_GAMEPAD_X; break;
  697. case 4: button = BC_GAMEPAD_Y; break;
  698. case 5: button = BC_GAMEPAD_LB; break;
  699. case 6: button = BC_GAMEPAD_RB; break;
  700. case 7: button = BC_GAMEPAD_LS; break;
  701. case 8: button = BC_GAMEPAD_RS; break;
  702. case 9: button = BC_GAMEPAD_START; break;
  703. case 10: button = BC_GAMEPAD_BACK; break;
  704. case 11: button = BC_GAMEPAD_BTN1; break;
  705. case 12: button = BC_GAMEPAD_DPAD_UP; break;
  706. case 13: button = BC_GAMEPAD_DPAD_DOWN; break;
  707. case 14: button = BC_GAMEPAD_DPAD_LEFT; break;
  708. case 15: button = BC_GAMEPAD_DPAD_RIGHT; break;
  709. default:
  710. {
  711. INT32 buttonIdx = usage - 16;
  712. if(buttonIdx < 19)
  713. button = (ButtonCode)((INT32)(BC_GAMEPAD_BTN2 + buttonIdx));
  714. }
  715. break;
  716. }
  717. }
  718. }
  719. else if(usagePage == kHIDPage_KeyboardOrKeypad)
  720. {
  721. // Usage -1 and 1 are special signals that happen along with every button press/release and should be
  722. // ignored
  723. if(usage != -1 && usage != 1)
  724. button = scanCodeToKeyCode((UINT32)usage);
  725. }
  726. if(button != BC_UNASSIGNED)
  727. {
  728. if(value != 0)
  729. mData.owner->_notifyButtonPressed(entry.id, button, timestamp);
  730. else
  731. mData.owner->_notifyButtonReleased(entry.id, button, timestamp);
  732. }
  733. CFRelease(valueRef);
  734. }
  735. }
  736. // Report mouse axes
  737. if (mData.type == HIDType::Mouse)
  738. {
  739. if (mData.mouseAxisValues[0] != 0 || mData.mouseAxisValues[1] != 0 || mData.mouseAxisValues[2] != 0)
  740. mData.owner->_notifyMouseMoved(mData.mouseAxisValues[0], mData.mouseAxisValues[1], mData.mouseAxisValues[2]);
  741. }
  742. }
  743. void Input::initRawInput()
  744. {
  745. mPlatformData = bs_new<InputPrivateData>();
  746. mKeyboard = bs_new<Keyboard>("Keyboard", this);
  747. mMouse = bs_new<Mouse>("Mouse", this);
  748. mPlatformData->gamepadHIDManager = bs_new<HIDManager>(HIDType::Gamepad, this);
  749. for(auto& entry : mPlatformData->gamepadInfos)
  750. {
  751. entry.hid = mPlatformData->gamepadHIDManager;
  752. mGamepads.push_back(bs_new<Gamepad>(entry.name, entry, this));
  753. }
  754. }
  755. void Input::cleanUpRawInput()
  756. {
  757. if (mMouse != nullptr)
  758. bs_delete(mMouse);
  759. if (mKeyboard != nullptr)
  760. bs_delete(mKeyboard);
  761. for (auto& gamepad : mGamepads)
  762. bs_delete(gamepad);
  763. bs_delete(mPlatformData->gamepadHIDManager);
  764. bs_delete(mPlatformData);
  765. }
  766. UINT32 Input::getDeviceCount(InputDevice device) const
  767. {
  768. switch(device)
  769. {
  770. case InputDevice::Keyboard: return 1;
  771. case InputDevice::Mouse: return 1;
  772. case InputDevice::Gamepad: return (UINT32)mPlatformData->gamepadInfos.size();
  773. case InputDevice::Count: return 0;
  774. }
  775. return 0;
  776. }
  777. }