swift
joystickmacos.mm
1 // SPDX-FileCopyrightText: Copyright (C) 2019 swift Project Community / Contributors
2 // SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-swift-pilot-client-1
3 
4 #include "joystickmacos.h"
5 #include "macosinpututils.h"
6 #include "misc/logmessage.h"
7 
8 #include <CoreFoundation/CoreFoundation.h>
9 #include <IOKit/hid/IOHIDUsageTables.h>
10 
11 #include <array>
12 
13 using namespace swift::misc;
14 using namespace swift::misc::input;
15 
16 namespace swift::input
17 {
18  CJoystickDevice::CJoystickDevice(QObject *parent) :
19  QObject(parent)
20  { }
21 
22  CJoystickDevice::~CJoystickDevice()
23  { }
24 
25  bool CJoystickDevice::init(const IOHIDDeviceRef device)
26  {
28  {
29  CLogMessage(this).error(u"Access denied for joystick input monitoring");
30  }
31  m_deviceRef = device;
32 
33  CFTypeRef property = IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductKey));
34  m_deviceName = QString::fromCFString(static_cast<CFStringRef>(property));
35 
36  CLogMessage(static_cast<CJoystickMacOS *>(nullptr)).debug() << "Found joystick device" << m_deviceName;
37 
38  CFArrayRef elements = IOHIDDeviceCopyMatchingElements(device, nullptr, kIOHIDOptionsTypeNone);
39 
40  for (int i = 0; i < CFArrayGetCount(elements); i++)
41  {
42  IOHIDElementRef elementRef = (IOHIDElementRef)CFArrayGetValueAtIndex(elements, i);
43  if (CFGetTypeID(elementRef) != IOHIDElementGetTypeID()) { continue; }
44 
45  const IOHIDElementType type = IOHIDElementGetType(elementRef);
46  if (type != kIOHIDElementTypeInput_Button) { continue; }
47 
48  const uint32_t page = IOHIDElementGetUsagePage(elementRef);
49 
50  if (page == kHIDPage_Button)
51  {
52  CLogMessage(static_cast<CJoystickMacOS *>(nullptr)).debug() << "Found joystick button " << m_joystickDeviceInputs.size();
53 
54  int number = m_joystickDeviceInputs.size();
55  m_joystickDeviceInputs.insert(elementRef, { m_deviceName, number });
56  IOHIDDeviceRegisterInputValueCallback(device, valueCallback, this);
57  }
58  }
59  CFRelease(elements);
60 
61  // Filter devices with 0 buttons
62  if (m_joystickDeviceInputs.isEmpty()) { return false; }
63 
64  CLogMessage(this).info(u"Created joystick device '%1' with %2 buttons") << m_deviceName << m_joystickDeviceInputs.size();
65  return true;
66  }
67 
69  {
70  return CSequence<CJoystickButton>(m_joystickDeviceInputs.values());
71  }
72 
73  void CJoystickDevice::processButtonEvent(IOHIDValueRef value)
74  {
75  IOHIDElementRef element = IOHIDValueGetElement(value);
76  CJoystickButton joystickButton = m_joystickDeviceInputs.value(element);
77  if (joystickButton.isValid())
78  {
79  bool isPressed = IOHIDValueGetIntegerValue(value) == 1;
80  if (isPressed) { emit buttonChanged(joystickButton, true); }
81  else { emit buttonChanged(joystickButton, false); }
82  }
83  }
84 
85  void CJoystickDevice::valueCallback(void *context, IOReturn result, void *sender, IOHIDValueRef value)
86  {
87  Q_UNUSED(result);
88  Q_UNUSED(sender);
89  CJoystickDevice *obj = static_cast<CJoystickDevice *>(context);
90  obj->processButtonEvent(value);
91  }
92 
93  CJoystickMacOS::CJoystickMacOS(QObject *parent) : IJoystick(parent)
94  { }
95 
97  {
98  for (CJoystickDevice *d : m_joystickDevices)
99  {
100  delete d;
101  }
102  m_joystickDevices.clear();
103 
104  if (m_hidManager)
105  {
106  IOHIDManagerClose(m_hidManager, kIOHIDOptionsTypeNone);
107  CFRelease(m_hidManager);
108  }
109  }
110 
112  {
113  CJoystickButtonList availableButtons;
114  for (const CJoystickDevice *device : std::as_const(m_joystickDevices))
115  {
116  availableButtons.push_back(device->getDeviceButtons());
117  }
118  return availableButtons;
119  }
120 
122  {
123  m_hidManager = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone);
124 
125  CFMutableArrayRef matchingArray = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
126  if (!matchingArray)
127  {
128  CLogMessage(this).warning(u"Cocoa: Failed to create array");
129  return false;
130  }
131 
132  CFDictionaryRef matchingDict = CMacOSInputUtils::createDeviceMatchingDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_Joystick);
133  if (matchingDict)
134  {
135  CFArrayAppendValue(matchingArray, matchingDict);
136  CFRelease(matchingDict);
137  }
138 
139  matchingDict = CMacOSInputUtils::createDeviceMatchingDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_GamePad);
140  if (matchingDict)
141  {
142  CFArrayAppendValue(matchingArray, matchingDict);
143  CFRelease(matchingDict);
144  }
145 
146  matchingDict = CMacOSInputUtils::createDeviceMatchingDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_MultiAxisController);
147  if (matchingDict)
148  {
149  CFArrayAppendValue(matchingArray, matchingDict);
150  CFRelease(matchingDict);
151  }
152 
153  IOHIDManagerSetDeviceMatchingMultiple(m_hidManager, matchingArray);
154  CFRelease(matchingArray);
155 
156  IOHIDManagerRegisterDeviceMatchingCallback(m_hidManager, matchCallback, this);
157  IOHIDManagerRegisterDeviceRemovalCallback(m_hidManager, removeCallback, this);
158  IOHIDManagerScheduleWithRunLoop(m_hidManager, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
159  return IOHIDManagerOpen(m_hidManager, kIOHIDOptionsTypeNone) == kIOReturnSuccess;
160  }
161 
162  void CJoystickMacOS::addJoystickDevice(const IOHIDDeviceRef device)
163  {
164  for (const CJoystickDevice *d : m_joystickDevices)
165  {
166  if (d->getNativeDevice() == device) { return; }
167  }
168 
169  CJoystickDevice *joystickDevice = new CJoystickDevice(this);
170  bool success = joystickDevice->init(device);
171  if (success)
172  {
173  connect(joystickDevice, &CJoystickDevice::buttonChanged, this, &CJoystickMacOS::joystickButtonChanged);
174  m_joystickDevices.push_back(joystickDevice);
175  }
176  else
177  {
178  delete joystickDevice;
179  }
180  }
181 
182  void CJoystickMacOS::removeJoystickDevice(const IOHIDDeviceRef device)
183  {
184  for (auto it = m_joystickDevices.begin(); it != m_joystickDevices.end();)
185  {
186  CJoystickDevice *d = *it;
187  if (d->getNativeDevice() == device)
188  {
189  delete d;
190  it = m_joystickDevices.erase(it);
191  }
192  else
193  {
194  ++it;
195  }
196  }
197  }
198 
199  void CJoystickMacOS::joystickButtonChanged(const CJoystickButton &joystickButton, bool isPressed)
200  {
201  CHotkeyCombination oldCombination(m_buttonCombination);
202  if (isPressed) { m_buttonCombination.addJoystickButton(joystickButton); }
203  else { m_buttonCombination.removeJoystickButton(joystickButton); }
204 
205  if (oldCombination != m_buttonCombination)
206  {
207  emit buttonCombinationChanged(m_buttonCombination);
208  }
209  }
210 
211  void CJoystickMacOS::matchCallback(void *context, IOReturn result, void *sender, IOHIDDeviceRef device)
212  {
213  Q_UNUSED(result);
214  Q_UNUSED(sender);
215  CJoystickMacOS *obj = static_cast<CJoystickMacOS *>(context);
216  obj->addJoystickDevice(device);
217  }
218 
219  void CJoystickMacOS::removeCallback(void *context, IOReturn result, void *sender, IOHIDDeviceRef device)
220  {
221  Q_UNUSED(result);
222  Q_UNUSED(sender);
223  CJoystickMacOS *obj = static_cast<CJoystickMacOS *>(context);
224  obj->removeJoystickDevice(device);
225  }
226 
227 } // namespace swift::input
Linux Joystick device.
Definition: joysticklinux.h:24
bool init(const IOHIDDeviceRef device)
Initialize device.
void buttonChanged(const QString &name, int index, bool isPressed)
Joystick button changed.
swift::misc::input::CJoystickButtonList getDeviceButtons() const
Get all available device buttons.
CJoystickDevice(const QString &path, QFile *fd, QObject *parent)
Constructor.
MacOS implemenation of IJoystick.
Definition: joystickmacos.h:57
CJoystickMacOS(CJoystickMacOS const &)=delete
Copy Constructor.
virtual bool init()
Initializes the platform joystick devices.
virtual ~CJoystickMacOS()
Destructor.
virtual swift::misc::input::CJoystickButtonList getAllAvailableJoystickButtons() const
Get all available joystick buttons.
static CFMutableDictionaryRef createDeviceMatchingDictionary(UInt32 usagePage, UInt32 usage)
Creates a new device matching dict using usagePage and usage.
static bool requestAccess()
Request OS permission for input monitoring access.
void buttonCombinationChanged(const swift::misc::input::CHotkeyCombination &)
Joystick button combination has changed.
Class for emitting a log message.
Definition: logmessage.h:27
Derived & warning(const char16_t(&format)[N])
Set the severity to warning, providing a format string.
Derived & error(const char16_t(&format)[N])
Set the severity to error, providing a format string.
Derived & debug()
Set the severity to debug.
Derived & info(const char16_t(&format)[N])
Set the severity to info, providing a format string.
void push_back(const T &value)
Appends an element at the end of the sequence.
Definition: sequence.h:305
Value object representing hotkey sequence.
void removeJoystickButton(CJoystickButton button)
Remove joystick button.
void addJoystickButton(const CJoystickButton &button)
Add joystick button.
Value object representing a joystick button.
Value object encapsulating a list of joystick buttons.
Free functions in swift::misc.