BsMacOSInput.cpp 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870
  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. // Parse device name
  192. CFTypeRef propertyRef = IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductKey));
  193. if(!propertyRef)
  194. propertyRef = IOHIDDeviceGetProperty(device, CFSTR(kIOHIDManufacturerKey));
  195. if(propertyRef)
  196. {
  197. char buffer[256];
  198. if(CFStringGetCString((CFStringRef)propertyRef, buffer, sizeof(buffer), kCFStringEncodingUTF8))
  199. newDevice.name = String(buffer);
  200. }
  201. // Parse device elements
  202. CFArrayRef elements = IOHIDDeviceCopyMatchingElements(device, nullptr, kIOHIDOptionsTypeNone);
  203. if(elements)
  204. {
  205. HIDAddElements(elements, &newDevice);
  206. CFRelease(elements);
  207. }
  208. // Create a queue
  209. newDevice.queueRef = IOHIDQueueCreate(kCFAllocatorDefault, device, 128, kIOHIDOptionsTypeNone);
  210. for(auto& button : newDevice.buttons)
  211. IOHIDQueueAddElement(newDevice.queueRef, button.ref);
  212. for(auto& hat : newDevice.hats)
  213. IOHIDQueueAddElement(newDevice.queueRef, hat.ref);
  214. IOHIDQueueStart(newDevice.queueRef);
  215. // Assign a device ID
  216. if(data->type == HIDType::Gamepad)
  217. {
  218. auto freeId = (UINT32)-1;
  219. auto numDevices = (UINT32)data->devices.size();
  220. for(UINT32 i = 0; i < numDevices; i++)
  221. {
  222. bool validId = true;
  223. for(auto& entry : data->devices)
  224. {
  225. if(entry.id == i)
  226. {
  227. validId = false;
  228. break;
  229. }
  230. }
  231. if(validId)
  232. {
  233. freeId = i;
  234. break;
  235. }
  236. }
  237. if(freeId == (UINT32)-1)
  238. freeId = numDevices;
  239. newDevice.id = freeId;
  240. }
  241. else // All keyboard/mouse devices are coalesced into a single device
  242. newDevice.id = 0;
  243. data->devices.push_back(newDevice);
  244. // Register the gamepad device with Input manager
  245. if(data->type == HIDType::Gamepad)
  246. {
  247. InputPrivateData* pvtData = data->owner->_getPrivateData();
  248. GamepadInfo gamepadInfo;
  249. gamepadInfo.name = newDevice.name;
  250. gamepadInfo.id = newDevice.id;
  251. gamepadInfo.deviceRef = newDevice.ref;
  252. gamepadInfo.hid = nullptr;
  253. pvtData->gamepadInfos.push_back(gamepadInfo);
  254. }
  255. }
  256. /** Callback triggered when an input device is removed. */
  257. static void HIDDeviceRemovedCallback(void* context, IOReturn result, void* sender, IOHIDDeviceRef device)
  258. {
  259. auto data = (HIDData*)context;
  260. auto iterFind = std::find_if(data->devices.begin(), data->devices.end(),
  261. [&device](const HIDDevice& v)
  262. {
  263. return v.ref == device;
  264. });
  265. if(iterFind != data->devices.end())
  266. {
  267. IOHIDQueueStop(iterFind->queueRef);
  268. CFRelease(iterFind->queueRef);
  269. // Unregister the gamepad device from the Input manager
  270. if(data->type == HIDType::Gamepad)
  271. {
  272. InputPrivateData* pvtData = data->owner->_getPrivateData();
  273. UINT32 deviceId = iterFind->id;
  274. auto iterFind2 = std::find_if(
  275. pvtData->gamepadInfos.begin(),
  276. pvtData->gamepadInfos.end(),
  277. [deviceId](const GamepadInfo& info)
  278. {
  279. return info.id == deviceId;
  280. });
  281. if(iterFind2 != pvtData->gamepadInfos.end())
  282. pvtData->gamepadInfos.erase(iterFind2);
  283. }
  284. data->devices.erase(iterFind);
  285. }
  286. }
  287. /** Reads the current value of a particular HID element (e.g. button, axis). */
  288. static INT32 HIDGetElementValue(const HIDDevice &device, const HIDElement &element)
  289. {
  290. IOHIDValueRef valueRef;
  291. if(IOHIDDeviceGetValue(device.ref, element.ref, &valueRef) != kIOReturnSuccess)
  292. return 0;
  293. auto value = (INT32) IOHIDValueGetIntegerValue(valueRef);
  294. if(value < element.detectedMin)
  295. element.detectedMin = value;
  296. if(value > element.detectedMax)
  297. element.detectedMax = value;
  298. return value;
  299. }
  300. /**
  301. * Reads the current value of a particular HID element (e.g. button, axis) and converts the value so it fits
  302. * the provided [min, max] range.
  303. */
  304. static INT32 HIDGetElementValueScaled(const HIDDevice &device, const HIDElement &element, INT32 min, INT32 max)
  305. {
  306. INT32 value = HIDGetElementValue(device, element);
  307. float deviceRange = element.detectedMax - element.detectedMin;
  308. if(deviceRange == 0.0f)
  309. return value;
  310. float normalizedRange = (value - element.detectedMin) / deviceRange;
  311. float targetRange = max - min;
  312. return (INT32)(normalizedRange * targetRange) + min;
  313. }
  314. /** Converts a keyboard scan key (as reported by the HID manager) into engine's ButtonCode. */
  315. static ButtonCode scanCodeToKeyCode(UINT32 scanCode)
  316. {
  317. switch(scanCode)
  318. {
  319. case 0x04: return BC_A;
  320. case 0x05: return BC_B;
  321. case 0x06: return BC_C;
  322. case 0x07: return BC_D;
  323. case 0x08: return BC_E;
  324. case 0x09: return BC_F;
  325. case 0x0a: return BC_G;
  326. case 0x0b: return BC_H;
  327. case 0x0c: return BC_I;
  328. case 0x0d: return BC_J;
  329. case 0x0e: return BC_K;
  330. case 0x0f: return BC_L;
  331. case 0x10: return BC_M;
  332. case 0x11: return BC_N;
  333. case 0x12: return BC_O;
  334. case 0x13: return BC_P;
  335. case 0x14: return BC_Q;
  336. case 0x15: return BC_R;
  337. case 0x16: return BC_S;
  338. case 0x17: return BC_T;
  339. case 0x18: return BC_U;
  340. case 0x19: return BC_V;
  341. case 0x1a: return BC_W;
  342. case 0x1b: return BC_X;
  343. case 0x1c: return BC_Y;
  344. case 0x1d: return BC_Z;
  345. case 0x1e: return BC_1;
  346. case 0x1f: return BC_2;
  347. case 0x20: return BC_3;
  348. case 0x21: return BC_4;
  349. case 0x22: return BC_5;
  350. case 0x23: return BC_6;
  351. case 0x24: return BC_7;
  352. case 0x25: return BC_8;
  353. case 0x26: return BC_9;
  354. case 0x27: return BC_0;
  355. case 0x28: return BC_RETURN;
  356. case 0x29: return BC_ESCAPE;
  357. case 0x2a: return BC_BACK;
  358. case 0x2b: return BC_TAB;
  359. case 0x2c: return BC_SPACE;
  360. case 0x2d: return BC_MINUS;
  361. case 0x2e: return BC_EQUALS;
  362. case 0x2f: return BC_LBRACKET;
  363. case 0x30: return BC_RBRACKET;
  364. case 0x31: return BC_BACKSLASH;
  365. case 0x32: return BC_GRAVE;
  366. case 0x33: return BC_SEMICOLON;
  367. case 0x34: return BC_APOSTROPHE;
  368. case 0x35: return BC_GRAVE;
  369. case 0x36: return BC_COMMA;
  370. case 0x37: return BC_PERIOD;
  371. case 0x38: return BC_SLASH;
  372. case 0x39: return BC_CAPITAL;
  373. case 0x3a: return BC_F1;
  374. case 0x3b: return BC_F2;
  375. case 0x3c: return BC_F3;
  376. case 0x3d: return BC_F4;
  377. case 0x3e: return BC_F5;
  378. case 0x3f: return BC_F6;
  379. case 0x40: return BC_F7;
  380. case 0x41: return BC_F8;
  381. case 0x42: return BC_F9;
  382. case 0x43: return BC_F10;
  383. case 0x44: return BC_F11;
  384. case 0x45: return BC_F12;
  385. case 0x46: return BC_SYSRQ;
  386. case 0x47: return BC_SCROLL;
  387. case 0x48: return BC_PAUSE;
  388. case 0x49: return BC_INSERT;
  389. case 0x4a: return BC_HOME;
  390. case 0x4b: return BC_PGUP;
  391. case 0x4c: return BC_DELETE;
  392. case 0x4d: return BC_END;
  393. case 0x4e: return BC_PGDOWN;
  394. case 0x4f: return BC_RIGHT;
  395. case 0x50: return BC_LEFT;
  396. case 0x51: return BC_DOWN;
  397. case 0x52: return BC_UP;
  398. case 0x53: return BC_NUMLOCK;
  399. case 0x54: return BC_DIVIDE;
  400. case 0x55: return BC_MULTIPLY;
  401. case 0x56: return BC_SUBTRACT;
  402. case 0x57: return BC_ADD;
  403. case 0x58: return BC_NUMPADENTER;
  404. case 0x59: return BC_NUMPAD1;
  405. case 0x5a: return BC_NUMPAD2;
  406. case 0x5b: return BC_NUMPAD3;
  407. case 0x5c: return BC_NUMPAD4;
  408. case 0x5d: return BC_NUMPAD5;
  409. case 0x5e: return BC_NUMPAD6;
  410. case 0x5f: return BC_NUMPAD7;
  411. case 0x60: return BC_NUMPAD8;
  412. case 0x61: return BC_NUMPAD9;
  413. case 0x62: return BC_NUMPAD0;
  414. case 0x63: return BC_NUMPADCOMMA;
  415. case 0x64: return BC_OEM_102;
  416. case 0x66: return BC_POWER;
  417. case 0x67: return BC_NUMPADEQUALS;
  418. case 0x68: return BC_F13;
  419. case 0x69: return BC_F14;
  420. case 0x6a: return BC_F15;
  421. case 0x78: return BC_STOP;
  422. case 0x7f: return BC_MUTE;
  423. case 0x80: return BC_VOLUMEUP;
  424. case 0x81: return BC_VOLUMEDOWN;
  425. case 0x85: return BC_NUMPADCOMMA;
  426. case 0x86: return BC_NUMPADEQUALS;
  427. case 0x89: return BC_YEN;
  428. case 0xe0: return BC_LCONTROL;
  429. case 0xe1: return BC_LSHIFT;
  430. case 0xe2: return BC_LMENU;
  431. case 0xe3: return BC_LWIN;
  432. case 0xe4: return BC_RCONTROL;
  433. case 0xe5: return BC_RSHIFT;
  434. case 0xe6: return BC_RMENU;
  435. case 0xe7: return BC_RWIN;
  436. case 0xe8: return BC_PLAYPAUSE;
  437. case 0xe9: return BC_MEDIASTOP;
  438. case 0xea: return BC_PREVTRACK;
  439. case 0xeb: return BC_NEXTTRACK;
  440. case 0xed: return BC_VOLUMEUP;
  441. case 0xee: return BC_VOLUMEDOWN;
  442. case 0xef: return BC_MUTE;
  443. case 0xf0: return BC_WEBSEARCH;
  444. case 0xf1: return BC_WEBBACK;
  445. case 0xf2: return BC_WEBFORWARD;
  446. case 0xf3: return BC_WEBSTOP;
  447. case 0xf4: return BC_WEBSEARCH;
  448. case 0xf8: return BC_SLEEP;
  449. case 0xf9: return BC_WAKE;
  450. case 0xfb: return BC_CALCULATOR;
  451. default:
  452. return BC_UNASSIGNED;
  453. }
  454. }
  455. HIDManager::HIDManager(HIDType type, Input* input)
  456. {
  457. mData.type = type;
  458. mData.owner = input;
  459. mHIDManager = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDManagerOptionNone);
  460. if(mHIDManager == nullptr)
  461. return;
  462. if(IOHIDManagerOpen(mHIDManager, kIOHIDOptionsTypeNone) != kIOReturnSuccess)
  463. return;
  464. UINT32 numEntries = 0;
  465. const void* entries[3];
  466. switch (type)
  467. {
  468. case HIDType::Keyboard:
  469. entries[0] = createHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_Keyboard);
  470. numEntries = 1;
  471. break;
  472. case HIDType::Mouse:
  473. entries[0] = createHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_Mouse);
  474. numEntries = 1;
  475. break;
  476. case HIDType::Gamepad:
  477. entries[0] = createHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_Joystick);
  478. entries[1] = createHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_GamePad);
  479. entries[2] = createHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_MultiAxisController);
  480. numEntries = 3;
  481. break;
  482. }
  483. CFArrayRef entryArray = CFArrayCreate(kCFAllocatorDefault, entries, numEntries, &kCFTypeArrayCallBacks);
  484. IOHIDManagerSetDeviceMatchingMultiple(mHIDManager, entryArray);
  485. IOHIDManagerRegisterDeviceMatchingCallback(mHIDManager, HIDDeviceAddedCallback, &mData);
  486. IOHIDManagerRegisterDeviceRemovalCallback(mHIDManager, HIDDeviceRemovedCallback, &mData);
  487. CFStringRef runLoopMode = getRunLoopMode(type);
  488. IOHIDManagerScheduleWithRunLoop(mHIDManager, CFRunLoopGetCurrent(), runLoopMode);
  489. while(CFRunLoopRunInMode(runLoopMode, 0, TRUE) == kCFRunLoopRunHandledSource)
  490. { /* Do nothing */ }
  491. for (UINT32 i = 0; i < numEntries; i++)
  492. {
  493. if (entries[i])
  494. CFRelease((CFTypeRef) entries[i]);
  495. }
  496. CFRelease(entryArray);
  497. }
  498. HIDManager::~HIDManager()
  499. {
  500. for(auto& device : mData.devices)
  501. {
  502. IOHIDQueueStop(device.queueRef);
  503. CFRelease(device.queueRef);
  504. }
  505. CFStringRef runLoopMode = getRunLoopMode(mData.type);
  506. IOHIDManagerUnscheduleFromRunLoop(mHIDManager, CFRunLoopGetCurrent(), runLoopMode);
  507. IOHIDManagerClose(mHIDManager, kIOHIDOptionsTypeNone);
  508. CFRelease(mHIDManager);
  509. }
  510. void HIDManager::capture(IOHIDDeviceRef device, bool ignoreEvents)
  511. {
  512. // First trigger any callbacks
  513. CFStringRef runLoopMode = getRunLoopMode(mData.type);
  514. while(CFRunLoopRunInMode(runLoopMode, 0, TRUE) == kCFRunLoopRunHandledSource)
  515. { /* Do nothing */ }
  516. for(auto& entry : mData.devices)
  517. {
  518. if(device != nullptr && entry.ref != device)
  519. continue;
  520. // Read non-queued elements
  521. if(!ignoreEvents)
  522. {
  523. INT32 relX, relY, relZ;
  524. relX = relY = relZ = 0;
  525. struct AxisState
  526. {
  527. bool moved;
  528. INT32 value;
  529. };
  530. AxisState axisValues[24];
  531. bs_zero_out(axisValues);
  532. for (auto& axis : entry.axes)
  533. {
  534. auto axisType = (InputAxis) -1;
  535. if (mData.type == HIDType::Gamepad)
  536. {
  537. INT32 axisValue = HIDGetElementValueScaled(entry, axis, Gamepad::MIN_AXIS, Gamepad::MAX_AXIS);
  538. INT32 lastInputAxis = (INT32) InputAxis::RightTrigger + 1;
  539. switch (axis.usage)
  540. {
  541. case kHIDUsage_GD_X:
  542. axisType = InputAxis::LeftStickX;
  543. break;
  544. case kHIDUsage_GD_Y:
  545. axisType = InputAxis::LeftStickY;
  546. break;
  547. case kHIDUsage_GD_Rx:
  548. axisType = InputAxis::RightStickX;
  549. break;
  550. case kHIDUsage_GD_Ry:
  551. axisType = InputAxis::RightStickY;
  552. break;
  553. case kHIDUsage_GD_Z:
  554. axisType = InputAxis::LeftTrigger;
  555. break;
  556. case kHIDUsage_GD_Rz:
  557. axisType = InputAxis::RightTrigger;
  558. break;
  559. case kHIDUsage_GD_Slider:
  560. axisType = (InputAxis) (lastInputAxis + 1);
  561. break;
  562. case kHIDUsage_GD_Dial:
  563. axisType = (InputAxis) (lastInputAxis + 2);
  564. break;
  565. case kHIDUsage_GD_Wheel:
  566. axisType = (InputAxis) (lastInputAxis + 3);
  567. break;
  568. case kHIDUsage_Sim_Rudder:
  569. axisType = (InputAxis) (lastInputAxis + 4);
  570. break;
  571. case kHIDUsage_Sim_Throttle:
  572. axisType = (InputAxis) (lastInputAxis + 5);
  573. break;
  574. case kHIDUsage_Sim_Accelerator:
  575. axisType = (InputAxis) (lastInputAxis + 6);
  576. break;
  577. case kHIDUsage_Sim_Brake:
  578. axisType = (InputAxis) (lastInputAxis + 7);
  579. break;
  580. default:
  581. break;
  582. }
  583. if((INT32)axisType < 24)
  584. {
  585. axisValues[(INT32)axisType].moved = true;
  586. axisValues[(INT32)axisType].value = axisValue;
  587. }
  588. }
  589. else if (mData.type == HIDType::Mouse)
  590. {
  591. INT32 axisValue = HIDGetElementValue(entry, axis);
  592. switch (axis.usage)
  593. {
  594. case kHIDUsage_GD_X:
  595. axisType = InputAxis::MouseX;
  596. relX += axisValue;
  597. break;
  598. case kHIDUsage_GD_Y:
  599. axisType = InputAxis::MouseY;
  600. relY += axisValue;
  601. break;
  602. case kHIDUsage_GD_Z:
  603. axisType = InputAxis::MouseZ;
  604. relZ += axisValue;
  605. break;
  606. default:
  607. break;
  608. }
  609. }
  610. }
  611. if(relX != 0 || relY != 0 || relZ != 0)
  612. mData.owner->_notifyMouseMoved(relX, relY, relZ);
  613. for(UINT32 i = 0; i < 24; i++)
  614. {
  615. if(axisValues[i].moved)
  616. mData.owner->_notifyAxisMoved(entry.id, (UINT32)i, axisValues[i].value);
  617. }
  618. }
  619. // Read queued elements
  620. while(true)
  621. {
  622. IOHIDValueRef valueRef = IOHIDQueueCopyNextValueWithTimeout(entry.queueRef, 0);
  623. if(!valueRef)
  624. break;
  625. if(ignoreEvents)
  626. continue;
  627. IOHIDElementRef elemRef = IOHIDValueGetElement(valueRef);
  628. auto value = (INT32) IOHIDValueGetIntegerValue(valueRef); // For buttons this is 1 when pressed, 0 when released
  629. UINT64 timestamp = IOHIDValueGetTimeStamp(valueRef);
  630. UINT32 usage = IOHIDElementGetUsage(elemRef);
  631. UINT32 usagePage = IOHIDElementGetUsagePage(elemRef);
  632. ButtonCode button = BC_UNASSIGNED;
  633. if(usagePage == kHIDPage_GenericDesktop)
  634. {
  635. if (usage == kHIDUsage_GD_Hatswitch)
  636. {
  637. switch (value)
  638. {
  639. case 0:
  640. button = BC_GAMEPAD_DPAD_UP;
  641. break;
  642. case 1:
  643. button = BC_GAMEPAD_DPAD_UPRIGHT;
  644. break;
  645. case 2:
  646. button = BC_GAMEPAD_DPAD_RIGHT;
  647. break;
  648. case 3:
  649. button = BC_GAMEPAD_DPAD_DOWNRIGHT;
  650. break;
  651. case 4:
  652. button = BC_GAMEPAD_DPAD_DOWN;
  653. break;
  654. case 5:
  655. button = BC_GAMEPAD_DPAD_DOWNLEFT;
  656. break;
  657. case 6:
  658. button = BC_GAMEPAD_DPAD_LEFT;
  659. break;
  660. case 7:
  661. button = BC_GAMEPAD_DPAD_UPLEFT;
  662. break;
  663. default:
  664. break;
  665. }
  666. }
  667. }
  668. else if(usagePage == kHIDPage_Button)
  669. {
  670. if(mData.type == HIDType::Mouse)
  671. {
  672. if (usage > 0 && usage <= BC_NumMouse)
  673. button = (ButtonCode) ((UINT32) BC_MOUSE_LEFT + usage - 1);
  674. }
  675. else if(mData.type == HIDType::Gamepad)
  676. {
  677. // These are based on the xbox controller:
  678. switch(usage)
  679. {
  680. case 0: break;
  681. case 1: button = BC_GAMEPAD_A; break;
  682. case 2: button = BC_GAMEPAD_B; break;
  683. case 3: button = BC_GAMEPAD_X; break;
  684. case 4: button = BC_GAMEPAD_Y; break;
  685. case 5: button = BC_GAMEPAD_LB; break;
  686. case 6: button = BC_GAMEPAD_RB; break;
  687. case 7: button = BC_GAMEPAD_LS; break;
  688. case 8: button = BC_GAMEPAD_RS; break;
  689. case 9: button = BC_GAMEPAD_START; break;
  690. case 10: button = BC_GAMEPAD_BACK; break;
  691. case 11: button = BC_GAMEPAD_BTN1; break;
  692. case 12: button = BC_GAMEPAD_DPAD_UP; break;
  693. case 13: button = BC_GAMEPAD_DPAD_DOWN; break;
  694. case 14: button = BC_GAMEPAD_DPAD_LEFT; break;
  695. case 15: button = BC_GAMEPAD_DPAD_RIGHT; break;
  696. default:
  697. {
  698. INT32 buttonIdx = usage - 16;
  699. if(buttonIdx < 19)
  700. button = (ButtonCode)((INT32)(BC_GAMEPAD_BTN2 + buttonIdx));
  701. }
  702. break;
  703. }
  704. }
  705. }
  706. else if(usagePage == kHIDPage_KeyboardOrKeypad)
  707. {
  708. // Usage -1 and 1 are special signals that happen along with every button press/release and should be
  709. // ignored
  710. if(usage != -1 && usage != 1)
  711. button = scanCodeToKeyCode((UINT32)value);
  712. }
  713. if(button != BC_UNASSIGNED)
  714. {
  715. if(value != 0)
  716. mData.owner->_notifyButtonPressed(entry.id, button, timestamp);
  717. else
  718. mData.owner->_notifyButtonReleased(entry.id, button, timestamp);
  719. }
  720. CFRelease(valueRef);
  721. }
  722. }
  723. }
  724. void Input::initRawInput()
  725. {
  726. mPlatformData = bs_new<InputPrivateData>();
  727. mKeyboard = bs_new<Keyboard>("Keyboard", this);
  728. mMouse = bs_new<Mouse>("Mouse", this);
  729. mPlatformData->gamepadHIDManager = bs_new<HIDManager>(HIDType::Gamepad, this);
  730. for(auto& entry : mPlatformData->gamepadInfos)
  731. {
  732. entry.hid = mPlatformData->gamepadHIDManager;
  733. mGamepads.push_back(bs_new<Gamepad>(entry.name, entry, this));
  734. }
  735. }
  736. void Input::cleanUpRawInput()
  737. {
  738. if (mMouse != nullptr)
  739. bs_delete(mMouse);
  740. if (mKeyboard != nullptr)
  741. bs_delete(mKeyboard);
  742. for (auto& gamepad : mGamepads)
  743. bs_delete(gamepad);
  744. bs_delete(mPlatformData->gamepadHIDManager);
  745. bs_delete(mPlatformData);
  746. }
  747. UINT32 Input::getDeviceCount(InputDevice device) const
  748. {
  749. switch(device)
  750. {
  751. case InputDevice::Keyboard: return 1;
  752. case InputDevice::Mouse: return 1;
  753. case InputDevice::Gamepad: return (UINT32)mPlatformData->gamepadInfos.size();
  754. case InputDevice::Count: return 0;
  755. }
  756. return 0;
  757. }
  758. }