swift
Loading...
Searching...
No Matches
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
13using namespace swift::misc;
14using namespace swift::misc::input;
15
16namespace swift::input
17{
21
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.
bool init(const IOHIDDeviceRef device)
Initialize device.
virtual ~CJoystickDevice()
Destructor.
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.
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.
Abstract interface for native joystick handling.
Definition joystick.h:22
void buttonCombinationChanged(const swift::misc::input::CHotkeyCombination &)
Joystick button combination has changed.
Class for emitting a log message.
Definition logmessage.h:27
Derived & debug()
Set the severity to debug.
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 & info(const char16_t(&format)[N])
Set the severity to info, providing a format string.
Generic sequential container with value semantics.
Definition sequence.h:86
void push_back(const T &value)
Appends an element at the end of the sequence.
Definition sequence.h:308
Value object representing a joystick button.
Value object encapsulating a list of joystick buttons.
Free functions in swift::misc.
T value(const Key &key) const const
QObject(QObject *parent)
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
QObject * parent() const const
QVariant property(const char *name) const const
QObject * sender() const const
QString fromCFString(CFStringRef string)