From 456f8a384eae840e43bfcbbcab4379c91fe49598 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 --- platform/macos/SCsub | 2 +- platform/macos/joypad_macos.cpp | 619 ------------------------------- platform/macos/joypad_macos.h | 90 +---- platform/macos/joypad_macos.mm | 353 ++++++++++++++++++ platform/macos/os_macos.h | 2 +- platform/macos/os_macos.mm | 4 +- platform/macos/platform_config.h | 7 + 7 files changed, 374 insertions(+), 703 deletions(-) delete mode 100644 platform/macos/joypad_macos.cpp create mode 100644 platform/macos/joypad_macos.mm diff --git a/platform/macos/SCsub b/platform/macos/SCsub index 30202e5de792..cc2f32828826 100644 --- a/platform/macos/SCsub +++ b/platform/macos/SCsub @@ -22,7 +22,7 @@ files = [ "godot_menu_item.mm", "dir_access_macos.mm", "tts_macos.mm", - "joypad_macos.cpp", + "joypad_macos.mm", "vulkan_context_macos.mm", "gl_manager_macos_angle.mm", "gl_manager_macos_legacy.mm", 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..2d084699c124 100644 --- a/platform/macos/joypad_macos.h +++ b/platform/macos/joypad_macos.h @@ -28,93 +28,23 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /**************************************************************************/ -#ifndef JOYPAD_MACOS_H -#define JOYPAD_MACOS_H +#import -#include "core/input/input.h" +@interface JoypadMacOSObserver : NSObject -#import -#import -#import -#import +- (void)startObserving; +- (void)startProcessing; +- (void)finishObserving; -struct rec_element { - IOHIDElementRef ref; - IOHIDElementCookie cookie; - - uint32_t usage = 0; - - int min = 0; - int max = 0; - - struct Comparator { - bool operator()(const rec_element p_a, const rec_element p_b) const { return p_a.usage < p_b.usage; } - }; -}; - -struct joypad { - IOHIDDeviceRef device_ref = nullptr; - - Vector axis_elements; - Vector button_elements; - Vector hat_elements; - - int id = 0; - bool offset_hat = false; - - 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; - - 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 + void start_processing(); +}; diff --git a/platform/macos/joypad_macos.mm b/platform/macos/joypad_macos.mm new file mode 100644 index 000000000000..739ec7d12306 --- /dev/null +++ b/platform/macos/joypad_macos.mm @@ -0,0 +1,353 @@ +/**************************************************************************/ +/* 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" + +#import "os_macos.h" + +#include "core/config/project_settings.h" +#include "core/os/keyboard.h" +#include "core/string/ustring.h" +#include "main/main.h" + +JoypadMacOS::JoypadMacOS() { + observer = [[JoypadMacOSObserver alloc] init]; + [observer startObserving]; +} + +JoypadMacOS::~JoypadMacOS() { + if (observer) { + [observer finishObserving]; + observer = nil; + } +} + +void JoypadMacOS::start_processing() { + if (observer) { + [observer startProcessing]; + } +} + +@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]; +} + +- (int)getJoyIdForController:(GCController *)controller { + NSArray *keys = [self.connectedJoypads allKeysForObject: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])); + + // add it to our dictionary, this will retain our controllers + [self.connectedJoypads setObject:controller 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.connectedJoypads allKeysForObject:controller] count] > 0) { + print_verbose("Controller is already registered."); + } else if (!self.isProcessing) { + [self.joypadsQueue addObject:controller]; + } 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.connectedJoypads allKeysForObject: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) { + GCController *controller = [self.connectedJoypads objectForKey:key]; + 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 (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.buttonOptions.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) + + ///@TODO need to add support for controllerPausedHandler which should be a + /// toggle +} + +@end diff --git a/platform/macos/os_macos.h b/platform/macos/os_macos.h index ae94b6296d54..9c47514952b4 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 1b8ca0134d2c..92051f12651e 100644 --- a/platform/macos/os_macos.mm +++ b/platform/macos/os_macos.mm @@ -139,7 +139,7 @@ } 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) { @@ -761,7 +761,7 @@ 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")