From 07313a08f41146e30005acfa784bdf005d23750b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jezer=20Mej=C3=ADa?= Date: Wed, 16 Aug 2023 02:42:12 -0600 Subject: [PATCH] Migrate macos controller API to GameController.h This should fix a lot of issues regarding to old controller API, such as vibration Haptics (vibrations) are only available in macOS 11+, so haptics are now processed in macOS 11+ only. Also, this doesn't interfere with controller's input as controller support is available in macOS 10.9+. Added a Note for macOS regarding vibration support --- doc/classes/Input.xml | 1 + platform/macos/SCsub | 2 +- platform/macos/detect.py | 4 +- platform/macos/joypad_macos.cpp | 619 ------------------------------- platform/macos/joypad_macos.h | 113 ++---- platform/macos/joypad_macos.mm | 608 ++++++++++++++++++++++++++++++ platform/macos/os_macos.h | 2 +- platform/macos/os_macos.mm | 4 +- platform/macos/platform_config.h | 7 + 9 files changed, 664 insertions(+), 696 deletions(-) delete mode 100644 platform/macos/joypad_macos.cpp create mode 100644 platform/macos/joypad_macos.mm diff --git a/doc/classes/Input.xml b/doc/classes/Input.xml index bb8481383573..984d426c0af6 100644 --- a/doc/classes/Input.xml +++ b/doc/classes/Input.xml @@ -383,6 +383,7 @@ Starts to vibrate the joypad. Joypads usually come with two rumble motors, a strong and a weak one. [param weak_magnitude] is the strength of the weak motor (between 0 and 1) and [param strong_magnitude] is the strength of the strong motor (between 0 and 1). [param duration] is the duration of the effect in seconds (a duration of 0 will try to play the vibration indefinitely). The vibration can be stopped early by calling [method stop_joy_vibration]. [b]Note:[/b] Not every hardware is compatible with long effect durations; it is recommended to restart an effect if it has to be played for more than a few seconds. + [b]Note:[/b] For macOS, vibration is only supported in macOS 11 and later. diff --git a/platform/macos/SCsub b/platform/macos/SCsub index 08783ee14aa8..355772fcd2a8 100644 --- a/platform/macos/SCsub +++ b/platform/macos/SCsub @@ -115,7 +115,7 @@ files = [ "godot_open_save_delegate.mm", "dir_access_macos.mm", "tts_macos.mm", - "joypad_macos.cpp", + "joypad_macos.mm", "rendering_context_driver_vulkan_macos.mm", "gl_manager_macos_angle.mm", "gl_manager_macos_legacy.mm", diff --git a/platform/macos/detect.py b/platform/macos/detect.py index 9150a527a5a5..ed9e59ae0538 100644 --- a/platform/macos/detect.py +++ b/platform/macos/detect.py @@ -208,7 +208,9 @@ def configure(env: "SConsEnvironment"): "-framework", "IOKit", "-framework", - "ForceFeedback", + "GameController", + "-framework", + "CoreHaptics", "-framework", "CoreVideo", "-framework", diff --git a/platform/macos/joypad_macos.cpp b/platform/macos/joypad_macos.cpp deleted file mode 100644 index 1fcd636a4b3a..000000000000 --- a/platform/macos/joypad_macos.cpp +++ /dev/null @@ -1,619 +0,0 @@ -/**************************************************************************/ -/* joypad_macos.cpp */ -/**************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/**************************************************************************/ -/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ -/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/**************************************************************************/ - -#include "joypad_macos.h" - -#include - -#define GODOT_JOY_LOOP_RUN_MODE CFSTR("GodotJoypad") - -static JoypadMacOS *self = nullptr; - -joypad::joypad() { - ff_constant_force.lMagnitude = 10000; - ff_effect.dwDuration = 0; - ff_effect.dwSamplePeriod = 0; - ff_effect.dwGain = 10000; - ff_effect.dwFlags = FFEFF_OBJECTOFFSETS; - ff_effect.dwTriggerButton = FFEB_NOTRIGGER; - ff_effect.dwStartDelay = 0; - ff_effect.dwTriggerRepeatInterval = 0; - ff_effect.lpEnvelope = nullptr; - ff_effect.cbTypeSpecificParams = sizeof(FFCONSTANTFORCE); - ff_effect.lpvTypeSpecificParams = &ff_constant_force; - ff_effect.dwSize = sizeof(ff_effect); -} - -void joypad::free() { - if (device_ref) { - IOHIDDeviceUnscheduleFromRunLoop(device_ref, CFRunLoopGetCurrent(), GODOT_JOY_LOOP_RUN_MODE); - } - if (ff_device) { - FFDeviceReleaseEffect(ff_device, ff_object); - FFReleaseDevice(ff_device); - ff_device = nullptr; - memfree(ff_axes); - memfree(ff_directions); - } -} - -bool joypad::has_element(IOHIDElementCookie p_cookie, Vector *p_list) const { - for (int i = 0; i < p_list->size(); i++) { - if (p_cookie == p_list->get(i).cookie) { - return true; - } - } - return false; -} - -int joypad::get_hid_element_state(rec_element *p_element) const { - int value = 0; - if (p_element && p_element->ref) { - IOHIDValueRef valueRef; - if (IOHIDDeviceGetValue(device_ref, p_element->ref, &valueRef) == kIOReturnSuccess) { - value = (SInt32)IOHIDValueGetIntegerValue(valueRef); - - // Record min and max for auto calibration. - if (value < p_element->min) { - p_element->min = value; - } - if (value > p_element->max) { - p_element->max = value; - } - } - } - return value; -} - -void joypad::add_hid_element(IOHIDElementRef p_element) { - const CFTypeID elementTypeID = p_element ? CFGetTypeID(p_element) : 0; - - if (p_element && (elementTypeID == IOHIDElementGetTypeID())) { - const IOHIDElementCookie cookie = IOHIDElementGetCookie(p_element); - const uint32_t usagePage = IOHIDElementGetUsagePage(p_element); - const uint32_t usage = IOHIDElementGetUsage(p_element); - Vector *list = nullptr; - - switch (IOHIDElementGetType(p_element)) { - case kIOHIDElementTypeInput_Misc: - case kIOHIDElementTypeInput_Button: - case kIOHIDElementTypeInput_Axis: { - switch (usagePage) { - case kHIDPage_GenericDesktop: - switch (usage) { - case kHIDUsage_GD_X: - case kHIDUsage_GD_Y: - case kHIDUsage_GD_Z: - case kHIDUsage_GD_Rx: - case kHIDUsage_GD_Ry: - case kHIDUsage_GD_Rz: - case kHIDUsage_GD_Slider: - case kHIDUsage_GD_Dial: - case kHIDUsage_GD_Wheel: - if (!has_element(cookie, &axis_elements)) { - list = &axis_elements; - } - break; - - case kHIDUsage_GD_Hatswitch: - if (!has_element(cookie, &hat_elements)) { - list = &hat_elements; - } - break; - case kHIDUsage_GD_DPadUp: - case kHIDUsage_GD_DPadDown: - case kHIDUsage_GD_DPadRight: - case kHIDUsage_GD_DPadLeft: - case kHIDUsage_GD_Start: - case kHIDUsage_GD_Select: - if (!has_element(cookie, &button_elements)) { - list = &button_elements; - } - break; - } - break; - - case kHIDPage_Simulation: - switch (usage) { - case kHIDUsage_Sim_Rudder: - case kHIDUsage_Sim_Throttle: - case kHIDUsage_Sim_Accelerator: - case kHIDUsage_Sim_Brake: - if (!has_element(cookie, &axis_elements)) { - list = &axis_elements; - } - break; - - default: - break; - } - break; - - case kHIDPage_Button: - case kHIDPage_Consumer: - if (!has_element(cookie, &button_elements)) { - list = &button_elements; - } - break; - - default: - break; - } - } break; - - case kIOHIDElementTypeCollection: { - CFArrayRef array = IOHIDElementGetChildren(p_element); - if (array) { - add_hid_elements(array); - } - } break; - - default: - break; - } - - if (list) { // Add to list. - rec_element element; - - element.ref = p_element; - element.usage = usage; - - element.min = (SInt32)IOHIDElementGetLogicalMin(p_element); - element.max = (SInt32)IOHIDElementGetLogicalMax(p_element); - element.cookie = IOHIDElementGetCookie(p_element); - list->push_back(element); - list->sort_custom(); - } - } -} - -static void hid_element_added(const void *p_value, void *p_parameter) { - joypad *joy = static_cast(p_parameter); - joy->add_hid_element((IOHIDElementRef)p_value); -} - -void joypad::add_hid_elements(CFArrayRef p_array) { - CFRange range = { 0, CFArrayGetCount(p_array) }; - CFArrayApplyFunction(p_array, range, hid_element_added, this); -} - -static void joypad_removed_callback(void *ctx, IOReturn res, void *sender, IOHIDDeviceRef ioHIDDeviceObject) { - self->_device_removed(res, ioHIDDeviceObject); -} - -static void joypad_added_callback(void *ctx, IOReturn res, void *sender, IOHIDDeviceRef ioHIDDeviceObject) { - self->_device_added(res, ioHIDDeviceObject); -} - -static bool is_joypad(IOHIDDeviceRef p_device_ref) { - int usage_page = 0; - int usage = 0; - CFTypeRef refCF = IOHIDDeviceGetProperty(p_device_ref, CFSTR(kIOHIDPrimaryUsagePageKey)); - if (refCF) { - CFNumberGetValue((CFNumberRef)refCF, kCFNumberSInt32Type, &usage_page); - } - if (usage_page != kHIDPage_GenericDesktop) { - return false; - } - - refCF = IOHIDDeviceGetProperty(p_device_ref, CFSTR(kIOHIDPrimaryUsageKey)); - if (refCF) { - CFNumberGetValue((CFNumberRef)refCF, kCFNumberSInt32Type, &usage); - } - if ((usage != kHIDUsage_GD_Joystick && - usage != kHIDUsage_GD_GamePad && - usage != kHIDUsage_GD_MultiAxisController)) { - return false; - } - return true; -} - -void JoypadMacOS::_device_added(IOReturn p_res, IOHIDDeviceRef p_device) { - if (p_res != kIOReturnSuccess || have_device(p_device)) { - return; - } - - joypad new_joypad; - if (is_joypad(p_device)) { - configure_joypad(p_device, &new_joypad); -#if MAC_OS_X_VERSION_MIN_REQUIRED < 1060 - if (IOHIDDeviceGetService) { -#endif - const io_service_t ioservice = IOHIDDeviceGetService(p_device); - if ((ioservice) && (FFIsForceFeedback(ioservice) == FF_OK) && new_joypad.config_force_feedback(ioservice)) { - new_joypad.ffservice = ioservice; - } -#if MAC_OS_X_VERSION_MIN_REQUIRED < 1060 - } -#endif - device_list.push_back(new_joypad); - } - IOHIDDeviceScheduleWithRunLoop(p_device, CFRunLoopGetCurrent(), GODOT_JOY_LOOP_RUN_MODE); -} - -void JoypadMacOS::_device_removed(IOReturn p_res, IOHIDDeviceRef p_device) { - int device = get_joy_ref(p_device); - ERR_FAIL_COND(device == -1); - - input->joy_connection_changed(device_list[device].id, false, ""); - device_list.write[device].free(); - device_list.remove_at(device); -} - -static String _hex_str(uint8_t p_byte) { - static const char *dict = "0123456789abcdef"; - char ret[3]; - ret[2] = 0; - - ret[0] = dict[p_byte >> 4]; - ret[1] = dict[p_byte & 0xF]; - - return ret; -} - -bool JoypadMacOS::configure_joypad(IOHIDDeviceRef p_device_ref, joypad *p_joy) { - p_joy->device_ref = p_device_ref; - // Get device name. - String name; - char c_name[256]; - CFTypeRef refCF = IOHIDDeviceGetProperty(p_device_ref, CFSTR(kIOHIDProductKey)); - if (!refCF) { - refCF = IOHIDDeviceGetProperty(p_device_ref, CFSTR(kIOHIDManufacturerKey)); - } - if ((!refCF) || (!CFStringGetCString((CFStringRef)refCF, c_name, sizeof(c_name), kCFStringEncodingUTF8))) { - name = "Unidentified Joypad"; - } else { - name = c_name; - } - - int id = input->get_unused_joy_id(); - ERR_FAIL_COND_V(id == -1, false); - p_joy->id = id; - int vendor = 0; - refCF = IOHIDDeviceGetProperty(p_device_ref, CFSTR(kIOHIDVendorIDKey)); - if (refCF) { - CFNumberGetValue((CFNumberRef)refCF, kCFNumberSInt32Type, &vendor); - } - - int product_id = 0; - refCF = IOHIDDeviceGetProperty(p_device_ref, CFSTR(kIOHIDProductIDKey)); - if (refCF) { - CFNumberGetValue((CFNumberRef)refCF, kCFNumberSInt32Type, &product_id); - } - - int version = 0; - refCF = IOHIDDeviceGetProperty(p_device_ref, CFSTR(kIOHIDVersionNumberKey)); - if (refCF) { - CFNumberGetValue((CFNumberRef)refCF, kCFNumberSInt32Type, &version); - } - - if (vendor && product_id) { - char uid[128]; - snprintf(uid, 128, "%08x%08x%08x%08x", OSSwapHostToBigInt32(3), OSSwapHostToBigInt32(vendor), OSSwapHostToBigInt32(product_id), OSSwapHostToBigInt32(version)); - input->joy_connection_changed(id, true, name, uid); - } else { - // Bluetooth device. - String guid = "05000000"; - for (int i = 0; i < 12; i++) { - if (i < name.size()) { - guid += _hex_str(name[i]); - } else { - guid += "00"; - } - } - input->joy_connection_changed(id, true, name, guid); - } - - CFArrayRef array = IOHIDDeviceCopyMatchingElements(p_device_ref, nullptr, kIOHIDOptionsTypeNone); - if (array) { - p_joy->add_hid_elements(array); - CFRelease(array); - } - // Xbox controller hat values start at 1 rather than 0. - p_joy->offset_hat = vendor == 0x45e && - (product_id == 0x0b05 || - product_id == 0x02e0 || - product_id == 0x02fd || - product_id == 0x0b13); - - return true; -} - -#define FF_ERR() \ - { \ - if (ret != FF_OK) { \ - FFReleaseDevice(ff_device); \ - ff_device = nullptr; \ - return false; \ - } \ - } -bool joypad::config_force_feedback(io_service_t p_service) { - HRESULT ret = FFCreateDevice(p_service, &ff_device); - ERR_FAIL_COND_V(ret != FF_OK, false); - - ret = FFDeviceSendForceFeedbackCommand(ff_device, FFSFFC_RESET); - FF_ERR(); - - ret = FFDeviceSendForceFeedbackCommand(ff_device, FFSFFC_SETACTUATORSON); - FF_ERR(); - - if (check_ff_features()) { - ret = FFDeviceCreateEffect(ff_device, kFFEffectType_ConstantForce_ID, &ff_effect, &ff_object); - FF_ERR(); - return true; - } - FFReleaseDevice(ff_device); - ff_device = nullptr; - return false; -} -#undef FF_ERR - -#define TEST_FF(ff) (features.supportedEffects & (ff)) -bool joypad::check_ff_features() { - FFCAPABILITIES features; - HRESULT ret = FFDeviceGetForceFeedbackCapabilities(ff_device, &features); - if (ret == FF_OK && (features.supportedEffects & FFCAP_ET_CONSTANTFORCE)) { - uint32_t val; - ret = FFDeviceGetForceFeedbackProperty(ff_device, FFPROP_FFGAIN, &val, sizeof(val)); - if (ret != FF_OK) { - return false; - } - int num_axes = features.numFfAxes; - ff_axes = (DWORD *)memalloc(sizeof(DWORD) * num_axes); - ff_directions = (LONG *)memalloc(sizeof(LONG) * num_axes); - - for (int i = 0; i < num_axes; i++) { - ff_axes[i] = features.ffAxes[i]; - ff_directions[i] = 0; - } - - ff_effect.cAxes = num_axes; - ff_effect.rgdwAxes = ff_axes; - ff_effect.rglDirection = ff_directions; - return true; - } - return false; -} - -static BitField process_hat_value(int p_min, int p_max, int p_value, bool p_offset_hat) { - int range = (p_max - p_min + 1); - int value = p_value - p_min; - BitField hat_value; - if (range == 4) { - value *= 2; - } - if (p_offset_hat) { - value -= 1; - } - - switch (value) { - case 0: - hat_value.set_flag(HatMask::UP); - break; - case 1: - hat_value.set_flag(HatMask::UP); - hat_value.set_flag(HatMask::RIGHT); - break; - case 2: - hat_value.set_flag(HatMask::RIGHT); - break; - case 3: - hat_value.set_flag(HatMask::DOWN); - hat_value.set_flag(HatMask::RIGHT); - break; - case 4: - hat_value.set_flag(HatMask::DOWN); - break; - case 5: - hat_value.set_flag(HatMask::DOWN); - hat_value.set_flag(HatMask::LEFT); - break; - case 6: - hat_value.set_flag(HatMask::LEFT); - break; - case 7: - hat_value.set_flag(HatMask::UP); - hat_value.set_flag(HatMask::LEFT); - break; - default: - break; - } - return hat_value; -} - -void JoypadMacOS::poll_joypads() const { - while (CFRunLoopRunInMode(GODOT_JOY_LOOP_RUN_MODE, 0, TRUE) == kCFRunLoopRunHandledSource) { - // No-op. Pending callbacks will fire. - } -} - -static float axis_correct(int p_value, int p_min, int p_max) { - // Convert to a value between -1.0f and 1.0f. - return 2.0f * (p_value - p_min) / (p_max - p_min) - 1.0f; -} - -void JoypadMacOS::process_joypads() { - poll_joypads(); - - for (int i = 0; i < device_list.size(); i++) { - joypad &joy = device_list.write[i]; - - for (int j = 0; j < joy.axis_elements.size(); j++) { - rec_element &elem = joy.axis_elements.write[j]; - int value = joy.get_hid_element_state(&elem); - input->joy_axis(joy.id, (JoyAxis)j, axis_correct(value, elem.min, elem.max)); - } - for (int j = 0; j < joy.button_elements.size(); j++) { - int value = joy.get_hid_element_state(&joy.button_elements.write[j]); - input->joy_button(joy.id, (JoyButton)j, (value >= 1)); - } - for (int j = 0; j < joy.hat_elements.size(); j++) { - rec_element &elem = joy.hat_elements.write[j]; - int value = joy.get_hid_element_state(&elem); - BitField hat_value = process_hat_value(elem.min, elem.max, value, joy.offset_hat); - input->joy_hat(joy.id, hat_value); - } - - if (joy.ffservice) { - uint64_t timestamp = input->get_joy_vibration_timestamp(joy.id); - if (timestamp > joy.ff_timestamp) { - Vector2 strength = input->get_joy_vibration_strength(joy.id); - float duration = input->get_joy_vibration_duration(joy.id); - if (strength.x == 0 && strength.y == 0) { - joypad_vibration_stop(joy.id, timestamp); - } else { - float gain = MAX(strength.x, strength.y); - joypad_vibration_start(joy.id, gain, duration, timestamp); - } - } - } - } -} - -void JoypadMacOS::joypad_vibration_start(int p_id, float p_magnitude, float p_duration, uint64_t p_timestamp) { - joypad *joy = &device_list.write[get_joy_index(p_id)]; - joy->ff_timestamp = p_timestamp; - joy->ff_effect.dwDuration = p_duration * FF_SECONDS; - joy->ff_effect.dwGain = p_magnitude * FF_FFNOMINALMAX; - FFEffectSetParameters(joy->ff_object, &joy->ff_effect, FFEP_DURATION | FFEP_GAIN); - FFEffectStart(joy->ff_object, 1, 0); -} - -void JoypadMacOS::joypad_vibration_stop(int p_id, uint64_t p_timestamp) { - joypad *joy = &device_list.write[get_joy_index(p_id)]; - joy->ff_timestamp = p_timestamp; - FFEffectStop(joy->ff_object); -} - -int JoypadMacOS::get_joy_index(int p_id) const { - for (int i = 0; i < device_list.size(); i++) { - if (device_list[i].id == p_id) { - return i; - } - } - return -1; -} - -int JoypadMacOS::get_joy_ref(IOHIDDeviceRef p_device) const { - for (int i = 0; i < device_list.size(); i++) { - if (device_list[i].device_ref == p_device) { - return i; - } - } - return -1; -} - -bool JoypadMacOS::have_device(IOHIDDeviceRef p_device) const { - for (int i = 0; i < device_list.size(); i++) { - if (device_list[i].device_ref == p_device) { - return true; - } - } - return false; -} - -static CFDictionaryRef create_match_dictionary(const UInt32 page, const UInt32 usage, int *okay) { - CFDictionaryRef retval = nullptr; - CFNumberRef pageNumRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &page); - CFNumberRef usageNumRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &usage); - - if (pageNumRef && usageNumRef) { - const void *keys[2] = { (void *)CFSTR(kIOHIDDeviceUsagePageKey), (void *)CFSTR(kIOHIDDeviceUsageKey) }; - const void *vals[2] = { (void *)pageNumRef, (void *)usageNumRef }; - retval = CFDictionaryCreate(kCFAllocatorDefault, keys, vals, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); - } - - if (pageNumRef) { - CFRelease(pageNumRef); - } - if (usageNumRef) { - CFRelease(usageNumRef); - } - - if (!retval) { - *okay = 0; - } - - return retval; -} - -void JoypadMacOS::config_hid_manager(CFArrayRef p_matching_array) const { - CFRunLoopRef runloop = CFRunLoopGetCurrent(); - IOReturn ret = IOHIDManagerOpen(hid_manager, kIOHIDOptionsTypeNone); - ERR_FAIL_COND(ret != kIOReturnSuccess); - - IOHIDManagerSetDeviceMatchingMultiple(hid_manager, p_matching_array); - IOHIDManagerRegisterDeviceMatchingCallback(hid_manager, joypad_added_callback, nullptr); - IOHIDManagerRegisterDeviceRemovalCallback(hid_manager, joypad_removed_callback, nullptr); - IOHIDManagerScheduleWithRunLoop(hid_manager, runloop, GODOT_JOY_LOOP_RUN_MODE); - - while (CFRunLoopRunInMode(GODOT_JOY_LOOP_RUN_MODE, 0, TRUE) == kCFRunLoopRunHandledSource) { - // No-op. Callback fires once per existing device. - } -} - -JoypadMacOS::JoypadMacOS(Input *in) { - self = this; - input = in; - - int okay = 1; - const void *vals[] = { - (void *)create_match_dictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_Joystick, &okay), - (void *)create_match_dictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_GamePad, &okay), - (void *)create_match_dictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_MultiAxisController, &okay), - }; - const size_t n_elements = sizeof(vals) / sizeof(vals[0]); - CFArrayRef array = okay ? CFArrayCreate(kCFAllocatorDefault, vals, n_elements, &kCFTypeArrayCallBacks) : nullptr; - - for (size_t i = 0; i < n_elements; i++) { - if (vals[i]) { - CFRelease((CFTypeRef)vals[i]); - } - } - - if (array) { - hid_manager = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone); - if (hid_manager) { - config_hid_manager(array); - } - CFRelease(array); - } -} - -JoypadMacOS::~JoypadMacOS() { - for (int i = 0; i < device_list.size(); i++) { - device_list.write[i].free(); - } - - IOHIDManagerUnscheduleFromRunLoop(hid_manager, CFRunLoopGetCurrent(), GODOT_JOY_LOOP_RUN_MODE); - IOHIDManagerClose(hid_manager, kIOHIDOptionsTypeNone); - CFRelease(hid_manager); - hid_manager = nullptr; -} diff --git a/platform/macos/joypad_macos.h b/platform/macos/joypad_macos.h index 6a2af11b5182..b37a1b24f3fa 100644 --- a/platform/macos/joypad_macos.h +++ b/platform/macos/joypad_macos.h @@ -28,93 +28,62 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /**************************************************************************/ -#ifndef JOYPAD_MACOS_H -#define JOYPAD_MACOS_H - #include "core/input/input.h" -#import -#import -#import -#import +#define Key _QKey +#import +#import +#undef Key -struct rec_element { - IOHIDElementRef ref; - IOHIDElementCookie cookie; +@interface JoypadMacOSObserver : NSObject - uint32_t usage = 0; +- (void)startObserving; +- (void)startProcessing; +- (void)finishObserving; - int min = 0; - int max = 0; +@end - struct Comparator { - bool operator()(const rec_element p_a, const rec_element p_b) const { return p_a.usage < p_b.usage; } - }; -}; +API_AVAILABLE(macosx(11)) +@interface RumbleMotor : NSObject +@property(strong, nonatomic) CHHapticEngine *engine; +@property(strong, nonatomic) id player; +@end -struct joypad { - IOHIDDeviceRef device_ref = nullptr; +API_AVAILABLE(macosx(11)) +@interface RumbleContext : NSObject +// High frequency motor, it's usually the right engine. +@property(strong, nonatomic) RumbleMotor *weak_motor; +// Low frequency motor, it's usually the left engine. +@property(strong, nonatomic) RumbleMotor *strong_motor; +@end - Vector axis_elements; - Vector button_elements; - Vector hat_elements; +// Controller support for macOS begins with macOS 10.9+, +// however haptics (vibrations) are only supported in macOS 11+. +@interface Joypad : NSObject - int id = 0; - bool offset_hat = false; +@property(assign, nonatomic) BOOL force_feedback; +@property(assign, nonatomic) NSInteger ff_effect_timestamp; +@property(strong, nonatomic) GCController *controller; +@property(strong, nonatomic) RumbleContext *rumble_context API_AVAILABLE(macosx(11)); - io_service_t ffservice = 0; // Interface for force feedback, 0 = no ff. - FFCONSTANTFORCE ff_constant_force; - FFDeviceObjectReference ff_device = nullptr; - FFEffectObjectReference ff_object = nullptr; - uint64_t ff_timestamp = 0; - LONG *ff_directions = nullptr; - FFEFFECT ff_effect; - DWORD *ff_axes = nullptr; +- (instancetype)init; +- (instancetype)init:(GCController *)controller; - void add_hid_elements(CFArrayRef p_array); - void add_hid_element(IOHIDElementRef p_element); - - bool has_element(IOHIDElementCookie p_cookie, Vector *p_list) const; - bool config_force_feedback(io_service_t p_service); - bool check_ff_features(); - - int get_hid_element_state(rec_element *p_element) const; - - void free(); - joypad(); -}; +@end class JoypadMacOS { - enum { - JOYPADS_MAX = 16, - }; - private: - Input *input = nullptr; - IOHIDManagerRef hid_manager; - - Vector device_list; - - bool have_device(IOHIDDeviceRef p_device) const; - bool configure_joypad(IOHIDDeviceRef p_device_ref, joypad *p_joy); - - int get_joy_index(int p_id) const; - int get_joy_ref(IOHIDDeviceRef p_device) const; - - void poll_joypads() const; - void config_hid_manager(CFArrayRef p_matching_array) const; - - void joypad_vibration_start(int p_id, float p_magnitude, float p_duration, uint64_t p_timestamp); - void joypad_vibration_stop(int p_id, uint64_t p_timestamp); + JoypadMacOSObserver *observer; public: - void process_joypads(); - - void _device_added(IOReturn p_res, IOHIDDeviceRef p_device); - void _device_removed(IOReturn p_res, IOHIDDeviceRef p_device); - - JoypadMacOS(Input *in); + JoypadMacOS(); ~JoypadMacOS(); -}; -#endif // JOYPAD_MACOS_H + API_AVAILABLE(macosx(11)) + void joypad_vibration_start(Joypad *p_joypad, float p_weak_magnitude, float p_strong_magnitude, float p_duration, uint64_t p_timestamp); + API_AVAILABLE(macosx(11)) + void joypad_vibration_stop(Joypad *p_joypad, uint64_t p_timestamp); + + void start_processing(); + void process_joypads(); +}; diff --git a/platform/macos/joypad_macos.mm b/platform/macos/joypad_macos.mm new file mode 100644 index 000000000000..2420bd73fb0d --- /dev/null +++ b/platform/macos/joypad_macos.mm @@ -0,0 +1,608 @@ +/**************************************************************************/ +/* joypad_macos.mm */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#import "joypad_macos.h" + +#include + +#import "os_macos.h" + +#include "core/config/project_settings.h" +#include "core/os/keyboard.h" +#include "core/string/ustring.h" +#include "main/main.h" + +@implementation RumbleMotor + +- (instancetype)initWithController:(GCController *)controller locality:(GCHapticsLocality)locality { + self = [super init]; + self.engine = [controller.haptics createEngineWithLocality:locality]; + self.player = nil; + return self; +} + +- (void)execute_pattern:(CHHapticPattern *)pattern { + NSError *error; + id player = [self.engine createPlayerWithPattern:pattern error:&error]; + + // When all players have stopped for an engine, stop the engine. + [self.engine notifyWhenPlayersFinished:^CHHapticEngineFinishedAction(NSError *_Nullable error) { + return CHHapticEngineFinishedActionStopEngine; + }]; + + self.player = player; + + // Starts the engine and returns if an error was encountered. + if (![self.engine startAndReturnError:&error]) { + print_verbose("Couldn't start controller haptic engine"); + return; + } + if (![self.player startAtTime:0 error:&error]) { + print_verbose("Couldn't execute controller haptic pattern"); + } +} + +- (void)stop { + NSError *error; + [self.player stopAtTime:0 error:&error]; + self.player = nil; +} + +@end + +@implementation RumbleContext + +- (instancetype)init { + self = [super init]; + self.weak_motor = nil; + self.strong_motor = nil; + return self; +} + +- (bool)hasMotors { + return self.weak_motor != nil && self.strong_motor != nil; +} +- (bool)hasActivePlayers { + if (![self hasMotors]) { + return NO; + } + return self.weak_motor.player != nil && self.strong_motor.player != nil; +} + +@end + +@implementation Joypad + +- (instancetype)init { + self = [super init]; + return self; +} +- (instancetype)init:(GCController *)controller { + self = [super init]; + self.controller = controller; + + if (@available(macOS 11, *)) { + // Haptics within the controller is only available in macOS 11+. + self.rumble_context = [[RumbleContext alloc] init]; + + // Create Weak and Strong motors for controller. + self.rumble_context.weak_motor = [[RumbleMotor alloc] initWithController:controller locality:GCHapticsLocalityRightHandle]; + self.rumble_context.strong_motor = [[RumbleMotor alloc] initWithController:controller locality:GCHapticsLocalityLeftHandle]; + + // If the rumble motors aren't available, disable force feedback. + if (![self.rumble_context hasMotors]) { + self.force_feedback = NO; + } else { + self.force_feedback = YES; + } + } else { + self.force_feedback = NO; + } + + self.ff_effect_timestamp = 0; + + return self; +} + +@end + +JoypadMacOS::JoypadMacOS() { + observer = [[JoypadMacOSObserver alloc] init]; + [observer startObserving]; +} + +JoypadMacOS::~JoypadMacOS() { + if (observer) { + [observer finishObserving]; + observer = nil; + } +} + +void JoypadMacOS::start_processing() { + if (observer) { + [observer startProcessing]; + } + process_joypads(); +} + +API_AVAILABLE(macosx(10.15)) +CHHapticPattern *get_vibration_pattern(float p_magnitude, float p_duration) { + // Creates a vibration pattern with an intensity and duration. + NSDictionary *hapticDict = @{ + CHHapticPatternKeyPattern : @[ + @{ + CHHapticPatternKeyEvent : @{ + CHHapticPatternKeyEventType : CHHapticEventTypeHapticContinuous, + CHHapticPatternKeyTime : @(CHHapticTimeImmediate), + CHHapticPatternKeyEventDuration : [NSNumber numberWithFloat:p_duration], + + CHHapticPatternKeyEventParameters : @[ + @{ + CHHapticPatternKeyParameterID : CHHapticEventParameterIDHapticIntensity, + CHHapticPatternKeyParameterValue : [NSNumber numberWithFloat:p_magnitude] + }, + ], + }, + }, + ], + }; + NSError *error; + CHHapticPattern *pattern = [[CHHapticPattern alloc] initWithDictionary:hapticDict error:&error]; + return pattern; +} + +void JoypadMacOS::joypad_vibration_start(Joypad *p_joypad, float p_weak_magnitude, float p_strong_magnitude, float p_duration, uint64_t p_timestamp) { + if (!p_joypad.force_feedback || p_weak_magnitude < 0.f || p_weak_magnitude > 1.f || p_strong_magnitude < 0.f || p_strong_magnitude > 1.f) { + return; + } + + // If there is active vibration players, stop them. + if ([p_joypad.rumble_context hasActivePlayers]) { + joypad_vibration_stop(p_joypad, p_timestamp); + } + + // Gets the default vibration pattern and creates a player for each motor. + CHHapticPattern *weak_pattern = get_vibration_pattern(p_weak_magnitude, p_duration); + CHHapticPattern *strong_pattern = get_vibration_pattern(p_strong_magnitude, p_duration); + + RumbleMotor *weak_motor = p_joypad.rumble_context.weak_motor; + RumbleMotor *strong_motor = p_joypad.rumble_context.strong_motor; + + [weak_motor execute_pattern:weak_pattern]; + [strong_motor execute_pattern:strong_pattern]; + + p_joypad.ff_effect_timestamp = p_timestamp; +} + +void JoypadMacOS::joypad_vibration_stop(Joypad *p_joypad, uint64_t p_timestamp) { + if (!p_joypad.force_feedback) { + return; + } + // If there is no active vibration players, exit. + if (![p_joypad.rumble_context hasActivePlayers]) { + return; + } + + RumbleMotor *weak_motor = p_joypad.rumble_context.weak_motor; + RumbleMotor *strong_motor = p_joypad.rumble_context.strong_motor; + + [weak_motor stop]; + [strong_motor stop]; + + p_joypad.ff_effect_timestamp = p_timestamp; +} + +@interface JoypadMacOSObserver () + +@property(assign, nonatomic) BOOL isObserving; +@property(assign, nonatomic) BOOL isProcessing; +@property(strong, nonatomic) NSMutableDictionary *connectedJoypads; +@property(strong, nonatomic) NSMutableArray *joypadsQueue; + +@end + +@implementation JoypadMacOSObserver + +- (instancetype)init { + self = [super init]; + + if (self) { + [self godot_commonInit]; + } + + return self; +} + +- (void)godot_commonInit { + self.isObserving = NO; + self.isProcessing = NO; +} + +- (void)startProcessing { + self.isProcessing = YES; + + for (GCController *controller in self.joypadsQueue) { + [self addMacOSJoypad:controller]; + } + + [self.joypadsQueue removeAllObjects]; +} + +- (void)startObserving { + if (self.isObserving) { + return; + } + + self.isObserving = YES; + + self.connectedJoypads = [NSMutableDictionary dictionary]; + self.joypadsQueue = [NSMutableArray array]; + + // Get told when controllers connect, this will be called right away for + // already connected controllers. + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(controllerWasConnected:) + name:GCControllerDidConnectNotification + object:nil]; + + // Get told when controllers disconnect. + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(controllerWasDisconnected:) + name:GCControllerDidDisconnectNotification + object:nil]; +} + +- (void)finishObserving { + if (self.isObserving) { + [[NSNotificationCenter defaultCenter] removeObserver:self]; + } + + self.isObserving = NO; + self.isProcessing = NO; + + self.connectedJoypads = nil; + self.joypadsQueue = nil; +} + +- (void)dealloc { + [self finishObserving]; +} + +- (NSArray *)getAllKeysForController:(GCController *)controller { + NSArray *keys = [self.connectedJoypads allKeys]; + NSMutableArray *final_keys = [NSMutableArray array]; + + for (NSNumber *key in keys) { + Joypad *joypad = [self.connectedJoypads objectForKey:key]; + if (joypad.controller == controller) { + [final_keys addObject:key]; + } + } + + return final_keys; +} + +- (int)getJoyIdForController:(GCController *)controller { + NSArray *keys = [self getAllKeysForController:controller]; + + for (NSNumber *key in keys) { + int joy_id = [key intValue]; + return joy_id; + } + + return -1; +} + +- (void)addMacOSJoypad:(GCController *)controller { + // Get a new id for our controller. + int joy_id = Input::get_singleton()->get_unused_joy_id(); + + if (joy_id == -1) { + print_verbose("Couldn't retrieve new joy ID."); + return; + } + + // Assign our player index. + if (controller.playerIndex == GCControllerPlayerIndexUnset) { + controller.playerIndex = [self getFreePlayerIndex]; + } + + // Tell Godot about our new controller. + Input::get_singleton()->joy_connection_changed(joy_id, true, String::utf8([controller.vendorName UTF8String])); + + Joypad *joypad = [[Joypad alloc] init:controller]; + + // Add it to our dictionary, this will retain our controllers. + [self.connectedJoypads setObject:joypad forKey:[NSNumber numberWithInt:joy_id]]; + + // Set our input handler. + [self setControllerInputHandler:controller]; +} + +- (void)controllerWasConnected:(NSNotification *)notification { + // Get our controller. + GCController *controller = (GCController *)notification.object; + + if (!controller) { + print_verbose("Couldn't retrieve new controller."); + return; + } + + if ([[self getAllKeysForController:controller] count] > 0) { + print_verbose("Controller is already registered."); + } else if (!self.isProcessing) { + Joypad *joypad = [[Joypad alloc] init:controller]; + [self.joypadsQueue addObject:joypad]; + } else { + [self addMacOSJoypad:controller]; + } +} + +- (void)controllerWasDisconnected:(NSNotification *)notification { + // Find our joystick, there should be only one in our dictionary. + GCController *controller = (GCController *)notification.object; + + if (!controller) { + return; + } + + NSArray *keys = [self getAllKeysForController:controller]; + for (NSNumber *key in keys) { + // Tell Godot this joystick is no longer there. + int joy_id = [key intValue]; + Input::get_singleton()->joy_connection_changed(joy_id, false, ""); + + // And remove it from our dictionary. + [self.connectedJoypads removeObjectForKey:key]; + } +} + +- (GCControllerPlayerIndex)getFreePlayerIndex { + bool have_player_1 = false; + bool have_player_2 = false; + bool have_player_3 = false; + bool have_player_4 = false; + + if (self.connectedJoypads == nil) { + NSArray *keys = [self.connectedJoypads allKeys]; + for (NSNumber *key in keys) { + Joypad *joypad = [self.connectedJoypads objectForKey:key]; + GCController *controller = joypad.controller; + if (controller.playerIndex == GCControllerPlayerIndex1) { + have_player_1 = true; + } else if (controller.playerIndex == GCControllerPlayerIndex2) { + have_player_2 = true; + } else if (controller.playerIndex == GCControllerPlayerIndex3) { + have_player_3 = true; + } else if (controller.playerIndex == GCControllerPlayerIndex4) { + have_player_4 = true; + } + } + } + + if (!have_player_1) { + return GCControllerPlayerIndex1; + } else if (!have_player_2) { + return GCControllerPlayerIndex2; + } else if (!have_player_3) { + return GCControllerPlayerIndex3; + } else if (!have_player_4) { + return GCControllerPlayerIndex4; + } else { + return GCControllerPlayerIndexUnset; + } +} + +- (void)setControllerInputHandler:(GCController *)controller { + // Hook in the callback handler for the correct gamepad profile. + // This is a bit of a weird design choice on Apples part. + // You need to select the most capable gamepad profile for the + // gamepad attached. + if (controller.extendedGamepad != nil) { + // The extended gamepad profile has all the input you could possibly find on + // a gamepad but will only be active if your gamepad actually has all of + // these... + _weakify(self); + _weakify(controller); + + controller.extendedGamepad.valueChangedHandler = ^(GCExtendedGamepad *gamepad, GCControllerElement *element) { + _strongify(self); + _strongify(controller); + + int joy_id = [self getJoyIdForController:controller]; + + if (element == gamepad.buttonA) { + Input::get_singleton()->joy_button(joy_id, JoyButton::A, + gamepad.buttonA.isPressed); + } else if (element == gamepad.buttonB) { + Input::get_singleton()->joy_button(joy_id, JoyButton::B, + gamepad.buttonB.isPressed); + } else if (element == gamepad.buttonX) { + Input::get_singleton()->joy_button(joy_id, JoyButton::X, + gamepad.buttonX.isPressed); + } else if (element == gamepad.buttonY) { + Input::get_singleton()->joy_button(joy_id, JoyButton::Y, + gamepad.buttonY.isPressed); + } else if (element == gamepad.leftShoulder) { + Input::get_singleton()->joy_button(joy_id, JoyButton::LEFT_SHOULDER, + gamepad.leftShoulder.isPressed); + } else if (element == gamepad.rightShoulder) { + Input::get_singleton()->joy_button(joy_id, JoyButton::RIGHT_SHOULDER, + gamepad.rightShoulder.isPressed); + } else if (element == gamepad.dpad) { + Input::get_singleton()->joy_button(joy_id, JoyButton::DPAD_UP, + gamepad.dpad.up.isPressed); + Input::get_singleton()->joy_button(joy_id, JoyButton::DPAD_DOWN, + gamepad.dpad.down.isPressed); + Input::get_singleton()->joy_button(joy_id, JoyButton::DPAD_LEFT, + gamepad.dpad.left.isPressed); + Input::get_singleton()->joy_button(joy_id, JoyButton::DPAD_RIGHT, + gamepad.dpad.right.isPressed); + } + + if (element == gamepad.leftThumbstick) { + float value = gamepad.leftThumbstick.xAxis.value; + Input::get_singleton()->joy_axis(joy_id, JoyAxis::LEFT_X, value); + value = -gamepad.leftThumbstick.yAxis.value; + Input::get_singleton()->joy_axis(joy_id, JoyAxis::LEFT_Y, value); + } else if (element == gamepad.rightThumbstick) { + float value = gamepad.rightThumbstick.xAxis.value; + Input::get_singleton()->joy_axis(joy_id, JoyAxis::RIGHT_X, value); + value = -gamepad.rightThumbstick.yAxis.value; + Input::get_singleton()->joy_axis(joy_id, JoyAxis::RIGHT_Y, value); + } else if (element == gamepad.leftTrigger) { + float value = gamepad.leftTrigger.value; + Input::get_singleton()->joy_axis(joy_id, JoyAxis::TRIGGER_LEFT, value); + } else if (element == gamepad.rightTrigger) { + float value = gamepad.rightTrigger.value; + Input::get_singleton()->joy_axis(joy_id, JoyAxis::TRIGGER_RIGHT, value); + } + + if (@available(macOS 10.14.1, *)) { + if (element == gamepad.leftThumbstickButton) { + Input::get_singleton()->joy_button(joy_id, JoyButton::LEFT_STICK, + gamepad.leftThumbstickButton.isPressed); + } else if (element == gamepad.rightThumbstickButton) { + Input::get_singleton()->joy_button(joy_id, JoyButton::RIGHT_STICK, + gamepad.rightThumbstickButton.isPressed); + } + } + + if (@available(macOS 10.15, *)) { + if (element == gamepad.buttonOptions) { + Input::get_singleton()->joy_button(joy_id, JoyButton::BACK, + gamepad.buttonOptions.isPressed); + } else if (element == gamepad.buttonMenu) { + Input::get_singleton()->joy_button(joy_id, JoyButton::START, + gamepad.buttonMenu.isPressed); + } + } + + if (@available(macOS 11, *)) { + if (element == gamepad.buttonHome) { + Input::get_singleton()->joy_button(joy_id, JoyButton::GUIDE, + gamepad.buttonHome.isPressed); + } + if ([gamepad isKindOfClass:[GCXboxGamepad class]]) { + GCXboxGamepad *xboxGamepad = (GCXboxGamepad *)gamepad; + if (element == xboxGamepad.paddleButton1) { + Input::get_singleton()->joy_button(joy_id, JoyButton::PADDLE1, + xboxGamepad.paddleButton1.isPressed); + } else if (element == xboxGamepad.paddleButton2) { + Input::get_singleton()->joy_button(joy_id, JoyButton::PADDLE2, + xboxGamepad.paddleButton2.isPressed); + } else if (element == xboxGamepad.paddleButton3) { + Input::get_singleton()->joy_button(joy_id, JoyButton::PADDLE3, + xboxGamepad.paddleButton3.isPressed); + } else if (element == xboxGamepad.paddleButton4) { + Input::get_singleton()->joy_button(joy_id, JoyButton::PADDLE4, + xboxGamepad.paddleButton4.isPressed); + } + } + } + + if (@available(macOS 12, *)) { + if ([gamepad isKindOfClass:[GCXboxGamepad class]]) { + GCXboxGamepad *xboxGamepad = (GCXboxGamepad *)gamepad; + if (element == xboxGamepad.buttonShare) { + Input::get_singleton()->joy_button(joy_id, JoyButton::MISC1, + xboxGamepad.buttonShare.isPressed); + } + } + } + }; + } else if (controller.microGamepad != nil) { + // Micro gamepads were added in macOS 10.11 and feature just 2 buttons and a d-pad. + _weakify(self); + _weakify(controller); + + controller.microGamepad.valueChangedHandler = ^(GCMicroGamepad *gamepad, GCControllerElement *element) { + _strongify(self); + _strongify(controller); + + int joy_id = [self getJoyIdForController:controller]; + + if (element == gamepad.buttonA) { + Input::get_singleton()->joy_button(joy_id, JoyButton::A, + gamepad.buttonA.isPressed); + } else if (element == gamepad.buttonX) { + Input::get_singleton()->joy_button(joy_id, JoyButton::X, + gamepad.buttonX.isPressed); + } else if (element == gamepad.dpad) { + Input::get_singleton()->joy_button(joy_id, JoyButton::DPAD_UP, + gamepad.dpad.up.isPressed); + Input::get_singleton()->joy_button(joy_id, JoyButton::DPAD_DOWN, + gamepad.dpad.down.isPressed); + Input::get_singleton()->joy_button(joy_id, JoyButton::DPAD_LEFT, + gamepad.dpad.left.isPressed); + Input::get_singleton()->joy_button(joy_id, JoyButton::DPAD_RIGHT, + gamepad.dpad.right.isPressed); + } + }; + } + + // TODO: Need to add support for controller.motion which gives us access to + // the orientation of the device (if supported). +} + +@end + +void JoypadMacOS::process_joypads() { + if (@available(macOS 11, *)) { + // Process vibrations in macOS 11+. + NSArray *keys = [observer.connectedJoypads allKeys]; + + for (NSNumber *key in keys) { + int id = key.intValue; + Joypad *joypad = [observer.connectedJoypads objectForKey:key]; + + if (joypad.force_feedback) { + Input *input = Input::get_singleton(); + uint64_t timestamp = input->get_joy_vibration_timestamp(id); + + if (timestamp > (unsigned)joypad.ff_effect_timestamp) { + Vector2 strength = input->get_joy_vibration_strength(id); + float duration = input->get_joy_vibration_duration(id); + if (duration == 0) { + duration = GCHapticDurationInfinite; + } + + if (strength.x == 0 && strength.y == 0) { + joypad_vibration_stop(joypad, timestamp); + } else { + joypad_vibration_start(joypad, strength.x, strength.y, duration, timestamp); + } + } + } + } + } +} diff --git a/platform/macos/os_macos.h b/platform/macos/os_macos.h index 9b20e51f4759..8088c3431c27 100644 --- a/platform/macos/os_macos.h +++ b/platform/macos/os_macos.h @@ -32,7 +32,7 @@ #define OS_MACOS_H #include "crash_handler_macos.h" -#include "joypad_macos.h" +#import "joypad_macos.h" #include "core/input/input.h" #import "drivers/coreaudio/audio_driver_coreaudio.h" diff --git a/platform/macos/os_macos.mm b/platform/macos/os_macos.mm index 934767db7402..29f433a51cdc 100644 --- a/platform/macos/os_macos.mm +++ b/platform/macos/os_macos.mm @@ -139,7 +139,7 @@ void OS_MacOS::finalize() { } void OS_MacOS::initialize_joypads() { - joypad_macos = memnew(JoypadMacOS(Input::get_singleton())); + joypad_macos = memnew(JoypadMacOS()); } void OS_MacOS::set_main_loop(MainLoop *p_main_loop) { @@ -769,7 +769,7 @@ void OS_MacOS::run() { if (DisplayServer::get_singleton()) { DisplayServer::get_singleton()->process_events(); // Get rid of pending events. } - joypad_macos->process_joypads(); + joypad_macos->start_processing(); if (Main::iteration()) { quit = true; diff --git a/platform/macos/platform_config.h b/platform/macos/platform_config.h index 1a571b689acb..01b0a12b5d33 100644 --- a/platform/macos/platform_config.h +++ b/platform/macos/platform_config.h @@ -31,3 +31,10 @@ #include #define PTHREAD_RENAME_SELF + +#define _weakify(var) __weak typeof(var) GDWeak_##var = var; +#define _strongify(var) \ + _Pragma("clang diagnostic push") \ + _Pragma("clang diagnostic ignored \"-Wshadow\"") \ + __strong typeof(var) var = GDWeak_##var; \ + _Pragma("clang diagnostic pop")