diff --git a/src/posix/cocoa/i_joystick.cpp b/src/posix/cocoa/i_joystick.cpp index 3e3b0e527..9b9487cf6 100644 --- a/src/posix/cocoa/i_joystick.cpp +++ b/src/posix/cocoa/i_joystick.cpp @@ -33,6 +33,7 @@ #include #include +#include #include #include @@ -76,6 +77,9 @@ FString ToFString(const CFStringRef string) } +// --------------------------------------------------------------------------- + + class IOKitJoystick : public IJoystickConfig { public: @@ -110,6 +114,8 @@ public: void UseAxesPolling(bool axesPolling); + io_object_t* GetNotificationPtr(); + private: IOHIDDeviceInterface** m_interface; IOHIDQueueInterface** m_queue; @@ -161,6 +167,8 @@ private: bool m_useAxesPolling; + io_object_t m_notification; + static const float DEFAULT_DEADZONE; static const float DEFAULT_SENSITIVITY; @@ -272,6 +280,7 @@ IOKitJoystick::IOKitJoystick(const io_object_t device) , m_queue(CreateDeviceQueue(m_interface)) , m_sensitivity(DEFAULT_SENSITIVITY) , m_useAxesPolling(true) +, m_notification(0) { if (NULL == m_interface || NULL == m_queue) { @@ -304,6 +313,11 @@ IOKitJoystick::~IOKitJoystick() { M_SaveJoystickConfig(this); + if (0 != m_notification) + { + IOObjectRelease(m_notification); + } + if (NULL != m_queue) { (*m_queue)->stop(m_queue); @@ -915,6 +929,12 @@ void IOKitJoystick::RemoveFromQueue(const IOHIDElementCookie cookie) } +io_object_t* IOKitJoystick::GetNotificationPtr() +{ + return &m_notification; +} + + // --------------------------------------------------------------------------- @@ -931,28 +951,75 @@ public: // Updates axes/buttons states void Update(); - // Rebuilds device list - void Rescan(); - void UseAxesPolling(bool axesPolling); private: - TArray m_joysticks; + typedef TDeletingArray JoystickList; + JoystickList m_joysticks; - void Rescan(int usagePage, int usage); + static const size_t NOTIFICATION_PORT_COUNT = 2; - void ReleaseJoysticks(); + IONotificationPortRef m_notificationPorts[NOTIFICATION_PORT_COUNT]; + io_iterator_t m_notifications [NOTIFICATION_PORT_COUNT]; + + // Rebuilds device list + void Rescan(int usagePage, int usage, size_t notificationPortIndex); + void AddDevices(IONotificationPortRef notificationPort, const io_iterator_t iterator); + + static void OnDeviceAttached(void* refcon, io_iterator_t iterator); + static void OnDeviceRemoved(void* refcon, io_service_t service, + natural_t messageType, void* messageArgument); }; +IOKitJoystickManager* s_joystickManager; + + IOKitJoystickManager::IOKitJoystickManager() { - Rescan(); + memset(m_notifications, 0, sizeof m_notifications); + + for (size_t i = 0; i < NOTIFICATION_PORT_COUNT; ++i) + { + m_notificationPorts[i] = IONotificationPortCreate(kIOMasterPortDefault); + + if (NULL == m_notificationPorts[i]) + { + Printf(TEXTCOLOR_RED "IONotificationPortCreate(%zu) failed\n", i); + return; + } + + CFRunLoopAddSource(CFRunLoopGetCurrent(), + IONotificationPortGetRunLoopSource(m_notificationPorts[i]), kCFRunLoopDefaultMode); + } + + Rescan(kHIDPage_GenericDesktop, kHIDUsage_GD_Joystick, 0); + Rescan(kHIDPage_GenericDesktop, kHIDUsage_GD_GamePad, 1); } IOKitJoystickManager::~IOKitJoystickManager() { - ReleaseJoysticks(); + for (size_t i = 0; i < NOTIFICATION_PORT_COUNT; ++i) + { + IONotificationPortRef& port = m_notificationPorts[i]; + + if (NULL != port) + { + CFRunLoopRemoveSource(CFRunLoopGetCurrent(), + IONotificationPortGetRunLoopSource(port), kCFRunLoopDefaultMode); + + IONotificationPortDestroy(port); + port = NULL; + } + + io_iterator_t& notification = m_notifications[i]; + + if (0 != notification) + { + IOObjectRelease(notification); + notification = NULL; + } + } } @@ -988,15 +1055,23 @@ void IOKitJoystickManager::Update() } -void IOKitJoystickManager::Rescan() +void IOKitJoystickManager::UseAxesPolling(const bool axesPolling) { - ReleaseJoysticks(); - - Rescan(kHIDPage_GenericDesktop, kHIDUsage_GD_Joystick); - Rescan(kHIDPage_GenericDesktop, kHIDUsage_GD_GamePad); + for (size_t i = 0, count = m_joysticks.Size(); i < count; ++i) + { + m_joysticks[i]->UseAxesPolling(axesPolling); + } } -void IOKitJoystickManager::Rescan(const int usagePage, const int usage) + +void PostDeviceChangeEvent() +{ + const event_t event = { EV_DeviceChange }; + D_PostEvent(&event); +} + + +void IOKitJoystickManager::Rescan(const int usagePage, const int usage, const size_t notificationPortIndex) { CFMutableDictionaryRef deviceMatching = IOServiceMatching(kIOHIDDeviceKey); @@ -1014,54 +1089,86 @@ void IOKitJoystickManager::Rescan(const int usagePage, const int usage) CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &usage); CFDictionarySetValue(deviceMatching, CFSTR(kIOHIDPrimaryUsageKey), usageRef); - io_iterator_t iterator = 0; - const kern_return_t matchResult = - IOServiceGetMatchingServices(kIOMasterPortDefault, deviceMatching, &iterator); + assert(notificationPortIndex < NOTIFICATION_PORT_COUNT); + io_iterator_t* iteratorPtr = &m_notifications[notificationPortIndex]; + + const IONotificationPortRef notificationPort = m_notificationPorts[notificationPortIndex]; + assert(NULL != notificationPort); + + const kern_return_t notificationResult = IOServiceAddMatchingNotification(notificationPort, + kIOFirstMatchNotification, deviceMatching, OnDeviceAttached, notificationPort, iteratorPtr); + + // IOServiceAddMatchingNotification() consumes one reference of matching dictionary + // Thus CFRelease(deviceMatching) is not needed CFRelease(usageRef); CFRelease(usagePageRef); - if (KERN_SUCCESS != matchResult) + if (KERN_SUCCESS != notificationResult) { - Printf(TEXTCOLOR_RED "IOServiceGetMatchingServices() failed with code %i\n", matchResult); - return; + Printf(TEXTCOLOR_RED "IOServiceAddMatchingNotification() failed with code %i\n", notificationResult); } + AddDevices(notificationPort, *iteratorPtr); +} + +void IOKitJoystickManager::AddDevices(const IONotificationPortRef notificationPort, const io_iterator_t iterator) +{ while (io_object_t device = IOIteratorNext(iterator)) { IOKitJoystick* joystick = new IOKitJoystick(device); m_joysticks.Push(joystick); + const kern_return_t notificationResult = IOServiceAddInterestNotification(notificationPort, + device, kIOGeneralInterest, OnDeviceRemoved, joystick, joystick->GetNotificationPtr()); + if (KERN_SUCCESS != notificationResult) + { + Printf(TEXTCOLOR_RED "IOServiceAddInterestNotification() failed with code %i\n", notificationResult); + } + IOObjectRelease(device); + + PostDeviceChangeEvent(); } - - IOObjectRelease(iterator); } -void IOKitJoystickManager::ReleaseJoysticks() +void IOKitJoystickManager::OnDeviceAttached(void* const refcon, const io_iterator_t iterator) { - for (size_t i = 0, count = m_joysticks.Size(); i (refcon); - m_joysticks.Clear(); + assert(NULL != s_joystickManager); + s_joystickManager->AddDevices(notificationPort, iterator); } - -void IOKitJoystickManager::UseAxesPolling(const bool axesPolling) +void IOKitJoystickManager::OnDeviceRemoved(void* const refcon, io_service_t, const natural_t messageType, void*) { - for (size_t i = 0, count = m_joysticks.Size(); i UseAxesPolling(axesPolling); + return; } + + assert(NULL != refcon); + IOKitJoystick* const joystick = static_cast(refcon); + + assert(NULL != s_joystickManager); + JoystickList& joysticks = s_joystickManager->m_joysticks; + + for (unsigned int i = 0, count = joysticks.Size(); i < count; ++i) + { + if (joystick == joysticks[i]) + { + joysticks.Delete(i); + break; + } + } + + delete joystick; + + PostDeviceChangeEvent(); } - -IOKitJoystickManager* s_joystickManager; - - } // unnamed namespace @@ -1102,7 +1209,7 @@ void I_GetJoysticks(TArray& sticks) void I_GetAxes(float axes[NUM_JOYAXIS]) { - for (size_t i = 0; i Rescan(); - } + // Does nothing, device list is always kept up-to-date return NULL; }