2
0
Эх сурвалжийг харах

[macos, ios]: Refactor joypad support into shared code, numerous bugfixes

Stuart Carnie 1 жил өмнө
parent
commit
cd17cb0110

+ 3 - 1
drivers/SCsub

@@ -26,7 +26,9 @@ if env["xaudio2"]:
         print_error("Target platform '{}' does not support the XAudio2 audio driver".format(env["platform"]))
         Exit(255)
     SConscript("xaudio2/SCsub")
-
+# Shared Apple platform drivers
+if env["platform"] in ["macos", "ios"]:
+    SConscript("apple/SCsub")
 # Midi drivers
 SConscript("alsamidi/SCsub")
 SConscript("coremidi/SCsub")

+ 7 - 0
drivers/apple/SCsub

@@ -0,0 +1,7 @@
+#!/usr/bin/env python
+from misc.utility.scons_hints import *
+
+Import("env")
+
+# Driver source files
+env.add_source_files(env.drivers_sources, "*.mm")

+ 32 - 11
platform/ios/joypad_ios.h → drivers/apple/joypad_apple.h

@@ -1,5 +1,5 @@
 /**************************************************************************/
-/*  joypad_ios.h                                                          */
+/*  joypad_apple.h                                                        */
 /**************************************************************************/
 /*                         This file is part of:                          */
 /*                             GODOT ENGINE                               */
@@ -28,23 +28,44 @@
 /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                 */
 /**************************************************************************/
 
+#include "core/input/input.h"
+
+#define Key _QKey
 #import <GameController/GameController.h>
+#undef Key
 
-@interface JoypadIOSObserver : NSObject
+@class GCController;
+class RumbleContext;
 
-- (void)startObserving;
-- (void)startProcessing;
-- (void)finishObserving;
+struct GameController {
+	int joy_id;
+	GCController *controller;
+	RumbleContext *rumble_context API_AVAILABLE(macos(11.0), ios(14.0), tvos(14.0)) = nil;
+	NSInteger ff_effect_timestamp = 0;
+	bool force_feedback = false;
 
-@end
+	GameController(int p_joy_id, GCController *p_controller);
+	~GameController();
+};
 
-class JoypadIOS {
+class JoypadApple {
 private:
-	JoypadIOSObserver *observer;
+	id<NSObject> connect_observer = nil;
+	id<NSObject> disconnect_observer = nil;
+	HashMap<int, GameController *> joypads;
+	HashMap<GCController *, int> controller_to_joy_id;
+
+	GCControllerPlayerIndex get_free_player_index();
+
+	void add_joypad(GCController *p_controller);
+	void remove_joypad(GCController *p_controller);
 
 public:
-	JoypadIOS();
-	~JoypadIOS();
+	JoypadApple();
+	~JoypadApple();
+
+	void joypad_vibration_start(GameController &p_joypad, float p_weak_magnitude, float p_strong_magnitude, float p_duration, uint64_t p_timestamp) API_AVAILABLE(macos(11.0), ios(14.0), tvos(14.0));
+	void joypad_vibration_stop(GameController &p_joypad, uint64_t p_timestamp) API_AVAILABLE(macos(11.0), ios(14.0), tvos(14.0));
 
-	void start_processing();
+	void process_joypads();
 };

+ 427 - 0
drivers/apple/joypad_apple.mm

@@ -0,0 +1,427 @@
+/**************************************************************************/
+/*  joypad_apple.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_apple.h"
+
+#include <CoreHaptics/CoreHaptics.h>
+#import <os/log.h>
+
+#include "core/config/project_settings.h"
+#include "main/main.h"
+
+class API_AVAILABLE(macos(11), ios(14.0), tvos(14.0)) RumbleMotor {
+	CHHapticEngine *engine;
+	id<CHHapticPatternPlayer> player;
+	bool is_started;
+
+	RumbleMotor(GCController *p_controller, GCHapticsLocality p_locality) {
+		engine = [p_controller.haptics createEngineWithLocality:p_locality];
+		engine.autoShutdownEnabled = YES;
+	}
+
+public:
+	static RumbleMotor *create(GCController *p_controller, GCHapticsLocality p_locality) {
+		if ([p_controller.haptics.supportedLocalities containsObject:p_locality]) {
+			return memnew(RumbleMotor(p_controller, p_locality));
+		}
+		return nullptr;
+	}
+
+	_ALWAYS_INLINE_ bool has_active_player() {
+		return player != nil;
+	}
+
+	void execute_pattern(CHHapticPattern *p_pattern) {
+		NSError *error;
+		if (!is_started) {
+			ERR_FAIL_COND_MSG(![engine startAndReturnError:&error], "Couldn't start controller haptic engine: " + String::utf8(error.localizedDescription.UTF8String));
+			is_started = YES;
+		}
+
+		player = [engine createPlayerWithPattern:p_pattern error:&error];
+		ERR_FAIL_COND_MSG(error, "Couldn't create controller haptic pattern player: " + String::utf8(error.localizedDescription.UTF8String));
+		ERR_FAIL_COND_MSG(![player startAtTime:CHHapticTimeImmediate error:&error], "Couldn't execute controller haptic pattern: " + String::utf8(error.localizedDescription.UTF8String));
+	}
+
+	void stop() {
+		id<CHHapticPatternPlayer> old_player = player;
+		player = nil;
+
+		NSError *error;
+		ERR_FAIL_COND_MSG(![old_player stopAtTime:CHHapticTimeImmediate error:&error], "Couldn't stop controller haptic pattern: " + String::utf8(error.localizedDescription.UTF8String));
+	}
+};
+
+class API_AVAILABLE(macos(11), ios(14.0), tvos(14.0)) RumbleContext {
+	RumbleMotor *weak_motor;
+	RumbleMotor *strong_motor;
+
+public:
+	RumbleContext(GCController *p_controller) {
+		weak_motor = RumbleMotor::create(p_controller, GCHapticsLocalityRightHandle);
+		strong_motor = RumbleMotor::create(p_controller, GCHapticsLocalityLeftHandle);
+	}
+
+	~RumbleContext() {
+		if (weak_motor) {
+			memdelete(weak_motor);
+		}
+		if (strong_motor) {
+			memdelete(strong_motor);
+		}
+	}
+
+	_ALWAYS_INLINE_ bool has_motors() {
+		return weak_motor != nullptr && strong_motor != nullptr;
+	}
+
+	_ALWAYS_INLINE_ bool has_active_players() {
+		if (!has_motors()) {
+			return false;
+		}
+		return (weak_motor && weak_motor->has_active_player()) || (strong_motor && strong_motor->has_active_player());
+	}
+
+	void stop() {
+		if (weak_motor) {
+			weak_motor->stop();
+		}
+		if (strong_motor) {
+			strong_motor->stop();
+		}
+	}
+
+	void play_weak_pattern(CHHapticPattern *p_pattern) {
+		if (weak_motor) {
+			weak_motor->execute_pattern(p_pattern);
+		}
+	}
+
+	void play_strong_pattern(CHHapticPattern *p_pattern) {
+		if (strong_motor) {
+			strong_motor->execute_pattern(p_pattern);
+		}
+	}
+};
+
+GameController::GameController(int p_joy_id, GCController *p_controller) :
+		joy_id(p_joy_id), controller(p_controller) {
+	force_feedback = NO;
+	if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) {
+		if (controller.haptics != nil) {
+			// Create a rumble context for the controller.
+			rumble_context = memnew(RumbleContext(p_controller));
+
+			// If the rumble motors aren't available, disable force feedback.
+			force_feedback = rumble_context->has_motors();
+		}
+	}
+
+	int l_joy_id = joy_id;
+
+	auto BUTTON = [l_joy_id](JoyButton p_button) {
+		return ^(GCControllerButtonInput *button, float value, BOOL pressed) {
+			Input::get_singleton()->joy_button(l_joy_id, p_button, pressed);
+		};
+	};
+
+	if (controller.extendedGamepad != nil) {
+		GCExtendedGamepad *gamepad = controller.extendedGamepad;
+
+		gamepad.buttonA.pressedChangedHandler = BUTTON(JoyButton::A);
+		gamepad.buttonB.pressedChangedHandler = BUTTON(JoyButton::B);
+		gamepad.buttonX.pressedChangedHandler = BUTTON(JoyButton::X);
+		gamepad.buttonY.pressedChangedHandler = BUTTON(JoyButton::Y);
+		gamepad.leftShoulder.pressedChangedHandler = BUTTON(JoyButton::LEFT_SHOULDER);
+		gamepad.rightShoulder.pressedChangedHandler = BUTTON(JoyButton::RIGHT_SHOULDER);
+		gamepad.dpad.up.pressedChangedHandler = BUTTON(JoyButton::DPAD_UP);
+		gamepad.dpad.down.pressedChangedHandler = BUTTON(JoyButton::DPAD_DOWN);
+		gamepad.dpad.left.pressedChangedHandler = BUTTON(JoyButton::DPAD_LEFT);
+		gamepad.dpad.right.pressedChangedHandler = BUTTON(JoyButton::DPAD_RIGHT);
+
+		gamepad.leftThumbstick.valueChangedHandler = ^(GCControllerDirectionPad *dpad, float xValue, float yValue) {
+			Input::get_singleton()->joy_axis(l_joy_id, JoyAxis::LEFT_X, xValue);
+			Input::get_singleton()->joy_axis(l_joy_id, JoyAxis::LEFT_Y, -yValue);
+		};
+
+		gamepad.rightThumbstick.valueChangedHandler = ^(GCControllerDirectionPad *dpad, float xValue, float yValue) {
+			Input::get_singleton()->joy_axis(l_joy_id, JoyAxis::RIGHT_X, xValue);
+			Input::get_singleton()->joy_axis(l_joy_id, JoyAxis::RIGHT_Y, -yValue);
+		};
+		gamepad.leftTrigger.valueChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) {
+			Input::get_singleton()->joy_axis(l_joy_id, JoyAxis::TRIGGER_LEFT, value);
+		};
+		gamepad.rightTrigger.valueChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) {
+			Input::get_singleton()->joy_axis(l_joy_id, JoyAxis::TRIGGER_RIGHT, value);
+		};
+
+		if (@available(macOS 10.14.1, iOS 12.1, tvOS 12.1, *)) {
+			gamepad.leftThumbstickButton.pressedChangedHandler = BUTTON(JoyButton::LEFT_STICK);
+			gamepad.rightThumbstickButton.pressedChangedHandler = BUTTON(JoyButton::RIGHT_STICK);
+		}
+
+		if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)) {
+			gamepad.buttonOptions.pressedChangedHandler = BUTTON(JoyButton::BACK);
+			gamepad.buttonMenu.pressedChangedHandler = BUTTON(JoyButton::START);
+		}
+
+		if (@available(macOS 11, iOS 14.0, tvOS 14.0, *)) {
+			gamepad.buttonHome.pressedChangedHandler = BUTTON(JoyButton::GUIDE);
+			if ([gamepad isKindOfClass:[GCXboxGamepad class]]) {
+				GCXboxGamepad *xboxGamepad = (GCXboxGamepad *)gamepad;
+				xboxGamepad.paddleButton1.pressedChangedHandler = BUTTON(JoyButton::PADDLE1);
+				xboxGamepad.paddleButton2.pressedChangedHandler = BUTTON(JoyButton::PADDLE2);
+				xboxGamepad.paddleButton3.pressedChangedHandler = BUTTON(JoyButton::PADDLE3);
+				xboxGamepad.paddleButton4.pressedChangedHandler = BUTTON(JoyButton::PADDLE4);
+			}
+		}
+
+		if (@available(macOS 12, iOS 15.0, tvOS 15.0, *)) {
+			if ([gamepad isKindOfClass:[GCXboxGamepad class]]) {
+				GCXboxGamepad *xboxGamepad = (GCXboxGamepad *)gamepad;
+				xboxGamepad.buttonShare.pressedChangedHandler = BUTTON(JoyButton::MISC1);
+			}
+		}
+	} else if (controller.microGamepad != nil) {
+		GCMicroGamepad *gamepad = controller.microGamepad;
+
+		gamepad.buttonA.pressedChangedHandler = BUTTON(JoyButton::A);
+		gamepad.buttonX.pressedChangedHandler = BUTTON(JoyButton::X);
+		gamepad.dpad.up.pressedChangedHandler = BUTTON(JoyButton::DPAD_UP);
+		gamepad.dpad.down.pressedChangedHandler = BUTTON(JoyButton::DPAD_DOWN);
+		gamepad.dpad.left.pressedChangedHandler = BUTTON(JoyButton::DPAD_LEFT);
+		gamepad.dpad.right.pressedChangedHandler = BUTTON(JoyButton::DPAD_RIGHT);
+	}
+
+	// TODO: Need to add support for controller.motion which gives us access to
+	// the orientation of the device (if supported).
+}
+
+GameController::~GameController() {
+	if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) {
+		if (rumble_context) {
+			memdelete(rumble_context);
+		}
+	}
+}
+
+JoypadApple::JoypadApple() {
+	connect_observer = [NSNotificationCenter.defaultCenter
+			addObserverForName:GCControllerDidConnectNotification
+						object:nil
+						 queue:NSOperationQueue.mainQueue
+					usingBlock:^(NSNotification *notification) {
+						GCController *controller = notification.object;
+						if (!controller) {
+							return;
+						}
+						add_joypad(controller);
+					}];
+
+	disconnect_observer = [NSNotificationCenter.defaultCenter
+			addObserverForName:GCControllerDidDisconnectNotification
+						object:nil
+						 queue:NSOperationQueue.mainQueue
+					usingBlock:^(NSNotification *notification) {
+						GCController *controller = notification.object;
+						if (!controller) {
+							return;
+						}
+						remove_joypad(controller);
+					}];
+
+	if (@available(macOS 11.3, iOS 14.5, tvOS 14.5, *)) {
+		GCController.shouldMonitorBackgroundEvents = YES;
+	}
+}
+
+JoypadApple::~JoypadApple() {
+	for (KeyValue<int, GameController *> &E : joypads) {
+		memdelete(E.value);
+		E.value = nullptr;
+	}
+
+	[NSNotificationCenter.defaultCenter removeObserver:connect_observer];
+	[NSNotificationCenter.defaultCenter removeObserver:disconnect_observer];
+}
+
+// Finds the rightmost set bit in a number, n.
+// variation of https://www.geeksforgeeks.org/position-of-rightmost-set-bit/
+int rightmost_one(int n) {
+	return __builtin_ctz(n & -n) + 1;
+}
+
+GCControllerPlayerIndex JoypadApple::get_free_player_index() {
+	// player_set will be a bitfield where each bit represents a player index.
+	__block uint32_t player_set = 0;
+	for (const KeyValue<GCController *, int> &E : controller_to_joy_id) {
+		player_set |= 1U << E.key.playerIndex;
+	}
+
+	// invert, as we want to find the first unset player index.
+	int n = rightmost_one((int)(~player_set));
+	if (n >= 5) {
+		return GCControllerPlayerIndexUnset;
+	}
+
+	return (GCControllerPlayerIndex)(n - 1);
+}
+
+void JoypadApple::add_joypad(GCController *p_controller) {
+	if (controller_to_joy_id.has(p_controller)) {
+		return;
+	}
+
+	// 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 (p_controller.playerIndex == GCControllerPlayerIndexUnset) {
+		p_controller.playerIndex = get_free_player_index();
+	}
+
+	// Tell Godot about our new controller.
+	Input::get_singleton()->joy_connection_changed(joy_id, true, String::utf8(p_controller.vendorName.UTF8String));
+
+	// Assign our player index.
+	joypads.insert(joy_id, memnew(GameController(joy_id, p_controller)));
+	controller_to_joy_id.insert(p_controller, joy_id);
+}
+
+void JoypadApple::remove_joypad(GCController *p_controller) {
+	if (!controller_to_joy_id.has(p_controller)) {
+		return;
+	}
+
+	int joy_id = controller_to_joy_id[p_controller];
+	controller_to_joy_id.erase(p_controller);
+
+	// Tell Godot this joystick is no longer there.
+	Input::get_singleton()->joy_connection_changed(joy_id, false, "");
+
+	// And remove it from our dictionary.
+	GameController **old = joypads.getptr(joy_id);
+	memdelete(*old);
+	*old = nullptr;
+	joypads.erase(joy_id);
+}
+
+API_AVAILABLE(macos(10.15), ios(13.0), tvos(14.0))
+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 JoypadApple::joypad_vibration_start(GameController &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->has_active_players()) {
+		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);
+
+	p_joypad.rumble_context->play_weak_pattern(weak_pattern);
+	p_joypad.rumble_context->play_strong_pattern(strong_pattern);
+
+	p_joypad.ff_effect_timestamp = p_timestamp;
+}
+
+void JoypadApple::joypad_vibration_stop(GameController &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->has_active_players()) {
+		return;
+	}
+
+	p_joypad.rumble_context->stop();
+
+	p_joypad.ff_effect_timestamp = p_timestamp;
+}
+
+void JoypadApple::process_joypads() {
+	if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) {
+		for (KeyValue<int, GameController *> &E : joypads) {
+			int id = E.key;
+			GameController &joypad = *E.value;
+
+			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);
+					}
+				}
+			}
+		}
+	}
+}

+ 0 - 1
platform/ios/SCsub

@@ -68,7 +68,6 @@ ios_lib = [
     "ios.mm",
     "rendering_context_driver_vulkan_ios.mm",
     "display_server_ios.mm",
-    "joypad_ios.mm",
     "godot_view.mm",
     "tts_ios.mm",
     "display_layer.mm",

+ 0 - 362
platform/ios/joypad_ios.mm

@@ -1,362 +0,0 @@
-/**************************************************************************/
-/*  joypad_ios.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_ios.h"
-
-#import "godot_view.h"
-#import "os_ios.h"
-
-#include "core/config/project_settings.h"
-#import "drivers/coreaudio/audio_driver_coreaudio.h"
-#include "main/main.h"
-
-JoypadIOS::JoypadIOS() {
-	observer = [[JoypadIOSObserver alloc] init];
-	[observer startObserving];
-}
-
-JoypadIOS::~JoypadIOS() {
-	if (observer) {
-		[observer finishObserving];
-		observer = nil;
-	}
-}
-
-void JoypadIOS::start_processing() {
-	if (observer) {
-		[observer startProcessing];
-	}
-}
-
-@interface JoypadIOSObserver ()
-
-@property(assign, nonatomic) BOOL isObserving;
-@property(assign, nonatomic) BOOL isProcessing;
-@property(strong, nonatomic) NSMutableDictionary *connectedJoypads;
-@property(strong, nonatomic) NSMutableArray *joypadsQueue;
-
-@end
-
-@implementation JoypadIOSObserver
-
-- (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 addiOSJoypad: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)addiOSJoypad:(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 addiOSJoypad: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 (@available(iOS 13, *)) {
-				// iOS uses 'buttonOptions' and 'buttonMenu' names for BACK and START joy buttons.
-				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(iOS 14, *)) {
-				// iOS uses 'buttonHome' for the GUIDE joy button.
-				if (element == gamepad.buttonHome) {
-					Input::get_singleton()->joy_button(joy_id, JoyButton::GUIDE,
-							gamepad.buttonHome.isPressed);
-				}
-			}
-		};
-	} else if (controller.microGamepad != nil) {
-		// micro gamepads were added in OS 9 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

+ 2 - 2
platform/ios/os_ios.h

@@ -34,8 +34,8 @@
 #ifdef IOS_ENABLED
 
 #import "ios.h"
-#import "joypad_ios.h"
 
+#import "drivers/apple/joypad_apple.h"
 #import "drivers/coreaudio/audio_driver_coreaudio.h"
 #include "drivers/unix/os_unix.h"
 #include "servers/audio_server.h"
@@ -58,7 +58,7 @@ private:
 
 	iOS *ios = nullptr;
 
-	JoypadIOS *joypad_ios = nullptr;
+	JoypadApple *joypad_apple = nullptr;
 
 	MainLoop *main_loop = nullptr;
 

+ 5 - 7
platform/ios/os_ios.mm

@@ -130,12 +130,12 @@ void OS_IOS::initialize_modules() {
 	ios = memnew(iOS);
 	Engine::get_singleton()->add_singleton(Engine::Singleton("iOS", ios));
 
-	joypad_ios = memnew(JoypadIOS);
+	joypad_apple = memnew(JoypadApple);
 }
 
 void OS_IOS::deinitialize_modules() {
-	if (joypad_ios) {
-		memdelete(joypad_ios);
+	if (joypad_apple) {
+		memdelete(joypad_apple);
 	}
 
 	if (ios) {
@@ -169,6 +169,8 @@ bool OS_IOS::iterate() {
 		DisplayServer::get_singleton()->process_events();
 	}
 
+	joypad_apple->process_joypads();
+
 	return Main::iteration();
 }
 
@@ -176,10 +178,6 @@ void OS_IOS::start() {
 	if (Main::start() == EXIT_SUCCESS) {
 		main_loop->initialize();
 	}
-
-	if (joypad_ios) {
-		joypad_ios->start_processing();
-	}
 }
 
 void OS_IOS::finalize() {

+ 0 - 1
platform/macos/SCsub

@@ -123,7 +123,6 @@ files = [
     "native_menu_macos.mm",
     "dir_access_macos.mm",
     "tts_macos.mm",
-    "joypad_macos.mm",
     "rendering_context_driver_vulkan_macos.mm",
     "gl_manager_macos_angle.mm",
     "gl_manager_macos_legacy.mm",

+ 0 - 89
platform/macos/joypad_macos.h

@@ -1,89 +0,0 @@
-/**************************************************************************/
-/*  joypad_macos.h                                                        */
-/**************************************************************************/
-/*                         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 "core/input/input.h"
-
-#define Key _QKey
-#import <CoreHaptics/CoreHaptics.h>
-#import <GameController/GameController.h>
-#undef Key
-
-@interface JoypadMacOSObserver : NSObject
-
-- (void)startObserving;
-- (void)startProcessing;
-- (void)finishObserving;
-
-@end
-
-API_AVAILABLE(macosx(11))
-@interface RumbleMotor : NSObject
-@property(strong, nonatomic) CHHapticEngine *engine;
-@property(strong, nonatomic) id<CHHapticPatternPlayer> player;
-@end
-
-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
-
-// Controller support for macOS begins with macOS 10.9+,
-// however haptics (vibrations) are only supported in macOS 11+.
-@interface Joypad : NSObject
-
-@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));
-
-- (instancetype)init;
-- (instancetype)init:(GCController *)controller;
-
-@end
-
-class JoypadMacOS {
-private:
-	JoypadMacOSObserver *observer;
-
-public:
-	JoypadMacOS();
-	~JoypadMacOS();
-
-	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();
-};

+ 0 - 611
platform/macos/joypad_macos.mm

@@ -1,611 +0,0 @@
-/**************************************************************************/
-/*  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 <Foundation/Foundation.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"
-
-@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<CHHapticPatternPlayer> 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];
-
-	if (@available(macOS 11.3, *)) {
-		GCController.shouldMonitorBackgroundEvents = YES;
-	}
-}
-
-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<NSNumber *, Joypad *> *connectedJoypads;
-@property(strong, nonatomic) NSMutableArray<GCController *> *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<NSNumber *> *)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) {
-		[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 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);
-					}
-				}
-			}
-		}
-	}
-}

+ 2 - 2
platform/macos/os_macos.h

@@ -32,16 +32,16 @@
 #define OS_MACOS_H
 
 #include "crash_handler_macos.h"
-#import "joypad_macos.h"
 
 #include "core/input/input.h"
+#import "drivers/apple/joypad_apple.h"
 #import "drivers/coreaudio/audio_driver_coreaudio.h"
 #import "drivers/coremidi/midi_driver_coremidi.h"
 #include "drivers/unix/os_unix.h"
 #include "servers/audio_server.h"
 
 class OS_MacOS : public OS_Unix {
-	JoypadMacOS *joypad_macos = nullptr;
+	JoypadApple *joypad_apple = nullptr;
 
 #ifdef COREAUDIO_ENABLED
 	AudioDriverCoreAudio audio_driver;

+ 4 - 4
platform/macos/os_macos.mm

@@ -142,13 +142,13 @@ void OS_MacOS::finalize() {
 
 	delete_main_loop();
 
-	if (joypad_macos) {
-		memdelete(joypad_macos);
+	if (joypad_apple) {
+		memdelete(joypad_apple);
 	}
 }
 
 void OS_MacOS::initialize_joypads() {
-	joypad_macos = memnew(JoypadMacOS());
+	joypad_apple = memnew(JoypadApple());
 }
 
 void OS_MacOS::set_main_loop(MainLoop *p_main_loop) {
@@ -834,7 +834,7 @@ void OS_MacOS::run() {
 				if (DisplayServer::get_singleton()) {
 					DisplayServer::get_singleton()->process_events(); // Get rid of pending events.
 				}
-				joypad_macos->start_processing();
+				joypad_apple->process_joypads();
 
 				if (Main::iteration()) {
 					quit = true;