Add SDL joystick input for iOS

This commit is contained in:
Nintorch 2026-01-28 14:29:28 +05:00
parent 8db94f7b5e
commit 73fcc0d645
14 changed files with 306 additions and 747 deletions

View file

@ -44,7 +44,7 @@
<description>
Clears the calibration information for the specified joypad's motion sensors, if it has any and if they were calibrated.
See [method start_joy_motion_sensors_calibration] for an example on how to use joypad motion sensors and calibration in your games.
[b]Note:[/b] This feature is only supported on Windows, Linux, and macOS.
[b]Note:[/b] This feature is only supported on Windows, Linux, macOS, and iOS.
</description>
</method>
<method name="flush_buffered_events">
@ -128,7 +128,7 @@
+Y ... -Y: bottom ... top;
+Z ... -Z: farther ... closer.
The gravity part value is measured as a vector with length of [code]9.8[/code] away from the center of the Earth, which is a negative Y value.
[b]Note:[/b] This feature is only supported on Windows, Linux, and macOS.
[b]Note:[/b] This feature is only supported on Windows, Linux, and macOS. On iOS, joypad accelerometer sensor reading is not supported due to OS limitations.
</description>
</method>
<method name="get_joy_axis" qualifiers="const">
@ -149,7 +149,7 @@
+Y ... -Y: bottom ... top;
+Z ... -Z: farther ... closer.
The gravity part value is measured as a vector with length of [code]9.8[/code] away from the center of the Earth, which is a negative Y value.
[b]Note:[/b] This feature is only supported on Windows, Linux, and macOS.
[b]Note:[/b] This feature is only supported on Windows, Linux, and macOS. On iOS, joypad accelerometer sensor reading is not supported due to OS limitations.
</description>
</method>
<method name="get_joy_guid" qualifiers="const">
@ -171,7 +171,7 @@
Y: Angular speed around the Y axis (yaw);
Z: Angular speed around the Z axis (roll).
See [method start_joy_motion_sensors_calibration] for an example on how to use joypad gyroscope and gyroscope calibration in your games.
[b]Note:[/b] This feature is only supported on Windows, Linux, and macOS.
[b]Note:[/b] This feature is only supported on Windows, Linux, macOS, and iOS.
</description>
</method>
<method name="get_joy_info" qualifiers="const">
@ -179,15 +179,15 @@
<param index="0" name="device" type="int" />
<description>
Returns a dictionary with extra platform-specific information about the device, e.g. the raw gamepad name from the OS or the Steam Input index.
On Windows, Linux, and macOS, the dictionary contains the following fields:
On Windows, Linux, macOS, and iOS, the dictionary contains the following fields:
[code]raw_name[/code]: The name of the controller as it came from the OS, before getting renamed by the controller database.
[code]vendor_id[/code]: The USB vendor ID of the device.
[code]product_id[/code]: The USB product ID of the device.
[code]serial_number[/code]: The serial number of the device. This key won't be present if the serial number is unavailable.
[code]steam_input_index[/code]: The Steam Input gamepad index, if the device is not a Steam Input device this key won't be present.
On Windows, the dictionary can have an additional field:
[code]xinput_index[/code]: The index of the controller in the XInput system. This key won't be present for devices not handled by XInput.
[b]Note:[/b] The returned dictionary is always empty on Android, iOS, visionOS, and Web.
The dictionary can also include the following fields under selected platforms:
[code]steam_input_index[/code]: The Steam Input gamepad index (Windows, Linux, and macOS only). If the device is not a Steam Input device this key won't be present.
[code]xinput_index[/code]: The index of the controller in the XInput system (Windows only). This key won't be present for devices not handled by XInput.
[b]Note:[/b] The returned dictionary is always empty on Android and Web.
</description>
</method>
<method name="get_joy_motion_sensors_calibration" qualifiers="const" experimental="">
@ -198,7 +198,7 @@
The dictionary contains the following fields:
[code]gyroscope_offset[/code]: average offset in gyroscope values from [constant Vector2.ZERO] in rad/s.
See [method start_joy_motion_sensors_calibration] for an example on how to use joypad motion sensors and calibration in your games.
[b]Note:[/b] This feature is only supported on Windows, Linux, and macOS.
[b]Note:[/b] This feature is only supported on Windows, Linux, macOS, and iOS.
</description>
</method>
<method name="get_joy_motion_sensors_rate" qualifiers="const" experimental="">
@ -206,7 +206,7 @@
<param index="0" name="device" type="int" />
<description>
Returns the joypad's motion sensor rate in Hz, if the joypad has motion sensors and they're currently enabled. See also [method set_joy_motion_sensors_enabled].
[b]Note:[/b] This feature is only supported on Windows, Linux, and macOS.
[b]Note:[/b] This feature is only supported on Windows, Linux, macOS, and iOS.
</description>
</method>
<method name="get_joy_name">
@ -274,7 +274,7 @@
<param index="0" name="device" type="int" />
<description>
Returns [code]true[/code] if the joypad has an LED light that can change colors and/or brightness. See also [method set_joy_light].
[b]Note:[/b] This feature is only supported on Windows, Linux, and macOS.
[b]Note:[/b] This feature is only supported on Windows, Linux, macOS, and iOS.
</description>
</method>
<method name="has_joy_motion_sensors" qualifiers="const" experimental="">
@ -282,7 +282,8 @@
<param index="0" name="device" type="int" />
<description>
Returns [code]true[/code] if the joypad has motion sensors (accelerometer and gyroscope).
[b]Note:[/b] This feature is only supported on Windows, Linux, and macOS.
[b]Note:[/b] On iOS, joypad accelerometer sensor reading is not supported due to OS limitations.
[b]Note:[/b] This feature is only supported on Windows, Linux, macOS, and iOS.
</description>
</method>
<method name="is_action_just_pressed" qualifiers="const">
@ -371,7 +372,7 @@
<description>
Returns [code]true[/code] if the joypad's motion sensors have been calibrated.
See [method start_joy_motion_sensors_calibration] for an example on how to use joypad motion sensors and calibration in your games.
[b]Note:[/b] This feature is only supported on Windows, Linux, and macOS.
[b]Note:[/b] This feature is only supported on Windows, Linux, macOS, and iOS.
</description>
</method>
<method name="is_joy_motion_sensors_calibrating" qualifiers="const" experimental="">
@ -380,7 +381,7 @@
<description>
Returns [code]true[/code] if the joypad's motion sensors are currently being calibrated.
See [method start_joy_motion_sensors_calibration] for an example on how to use joypad motion sensors and calibration in your games.
[b]Note:[/b] This feature is only supported on Windows, Linux, and macOS.
[b]Note:[/b] This feature is only supported on Windows, Linux, macOS, and iOS.
</description>
</method>
<method name="is_joy_motion_sensors_enabled" qualifiers="const" experimental="">
@ -389,7 +390,7 @@
<description>
Returns [code]true[/code] if the requested joypad has motion sensors (accelerometer and gyroscope) and they are currently enabled. See also [method set_joy_motion_sensors_enabled] and [method has_joy_motion_sensors].
See [method start_joy_motion_sensors_calibration] for an example on how to use joypad motion sensors and calibration in your games.
[b]Note:[/b] This feature is only supported on Windows, Linux, and macOS.
[b]Note:[/b] This feature is only supported on Windows, Linux, macOS, and iOS.
</description>
</method>
<method name="is_key_label_pressed" qualifiers="const">
@ -508,7 +509,7 @@
<description>
Sets the joypad's LED light, if available, to the specified color. See also [method has_joy_light].
[b]Note:[/b] There is no way to get the color of the light from a joypad. If you need to know the assigned color, store it separately.
[b]Note:[/b] This feature is only supported on Windows, Linux, and macOS.
[b]Note:[/b] This feature is only supported on Windows, Linux, macOS, and iOS.
</description>
</method>
<method name="set_joy_motion_sensors_calibration" experimental="">
@ -518,7 +519,7 @@
<description>
Sets the specified joypad's calibration information. See also [method get_joy_motion_sensors_calibration].
See [method start_joy_motion_sensors_calibration] for an example on how to use joypad motion sensors and calibration in your games.
[b]Note:[/b] This feature is only supported on Windows, Linux, and macOS.
[b]Note:[/b] This feature is only supported on Windows, Linux, macOS, and iOS.
</description>
</method>
<method name="set_joy_motion_sensors_enabled" experimental="">
@ -529,7 +530,7 @@
Enables or disables the motion sensors (accelerometer and gyroscope), if available, on the specified joypad.
See [method start_joy_motion_sensors_calibration] for an example on how to use joypad motion sensors and calibration in your games.
It's recommended to disable the motion sensors when they're no longer being used, because otherwise it might drain the controller battery faster.
[b]Note:[/b] This feature is only supported on Windows, Linux, and macOS.
[b]Note:[/b] This feature is only supported on Windows, Linux, macOS, and iOS.
</description>
</method>
<method name="set_magnetometer">
@ -654,7 +655,7 @@
[/csharp]
[/codeblocks]
[b]Note:[/b] Accelerometer sensor doesn't usually require calibration.
[b]Note:[/b] This feature is only supported on Windows, Linux, and macOS.
[b]Note:[/b] This feature is only supported on Windows, Linux, macOS, and iOS.
</description>
</method>
<method name="start_joy_vibration">
@ -675,7 +676,7 @@
<description>
Stops the calibration process of the specified joypad's motion sensors.
See [method start_joy_motion_sensors_calibration] for an example on how to use joypad motion sensors and calibration in your games.
[b]Note:[/b] This feature is only supported on Windows, Linux, and macOS.
[b]Note:[/b] This feature is only supported on Windows, Linux, macOS, and iOS.
</description>
</method>
<method name="stop_joy_vibration">

View file

@ -61,8 +61,8 @@ if env["metal"]:
SConscript("metal/SCsub")
# Input drivers
if env["sdl"] and env["platform"] in ["linuxbsd", "macos", "windows"]:
# TODO: Evaluate support for Android, iOS, and Web.
if env["sdl"] and env["platform"] in ["linuxbsd", "macos", "windows", "ios", "visionos"]:
# TODO: Evaluate support for Android and Web.
SConscript("sdl/SCsub")
# Core dependencies

View file

@ -1,80 +0,0 @@
/**************************************************************************/
/* joypad_apple.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. */
/**************************************************************************/
#pragma once
#include "core/input/input.h"
#include "core/input/input_enums.h"
#define Key _QKey
#import <GameController/GameController.h>
#undef Key
@class GCController;
class RumbleContext;
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;
bool double_nintendo_joycon_layout = false;
bool single_nintendo_joycon_layout = false;
uint32_t axis_changed_mask = 0;
static_assert(static_cast<uint32_t>(JoyAxis::MAX) < 32, "JoyAxis::MAX must be less than 32");
double axis_value[(int)JoyAxis::MAX];
GameController(int p_joy_id, GCController *p_controller);
~GameController();
};
class JoypadApple {
private:
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:
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 process_joypads();
};

View file

@ -1,634 +0,0 @@
/**************************************************************************/
/* 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"
#import <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;
for (int i = 0; i < (int)JoyAxis::MAX; i++) {
axis_value[i] = 0.0;
}
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();
}
}
if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)) {
if ([controller.productCategory isEqualToString:@"Switch Pro Controller"] || [controller.productCategory isEqualToString:@"Nintendo Switch Joy-Con (L/R)"]) {
double_nintendo_joycon_layout = true;
}
if ([controller.productCategory isEqualToString:@"Nintendo Switch Joy-Con (L)"] || [controller.productCategory isEqualToString:@"Nintendo Switch Joy-Con (R)"]) {
single_nintendo_joycon_layout = true;
}
}
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);
};
};
auto JOYSTICK_LEFT = ^(GCControllerDirectionPad *dpad, float xValue, float yValue) {
if (axis_value[(int)JoyAxis::LEFT_X] != xValue) {
axis_changed_mask |= (1 << (int)JoyAxis::LEFT_X);
axis_value[(int)JoyAxis::LEFT_X] = xValue;
}
if (axis_value[(int)JoyAxis::LEFT_Y] != -yValue) {
axis_changed_mask |= (1 << (int)JoyAxis::LEFT_Y);
axis_value[(int)JoyAxis::LEFT_Y] = -yValue;
}
};
auto JOYSTICK_RIGHT = ^(GCControllerDirectionPad *dpad, float xValue, float yValue) {
if (axis_value[(int)JoyAxis::RIGHT_X] != xValue) {
axis_changed_mask |= (1 << (int)JoyAxis::RIGHT_X);
axis_value[(int)JoyAxis::RIGHT_X] = xValue;
}
if (axis_value[(int)JoyAxis::RIGHT_Y] != -yValue) {
axis_changed_mask |= (1 << (int)JoyAxis::RIGHT_Y);
axis_value[(int)JoyAxis::RIGHT_Y] = -yValue;
}
};
auto TRIGGER_LEFT = ^(GCControllerButtonInput *button, float value, BOOL pressed) {
if (axis_value[(int)JoyAxis::TRIGGER_LEFT] != value) {
axis_changed_mask |= (1 << (int)JoyAxis::TRIGGER_LEFT);
axis_value[(int)JoyAxis::TRIGGER_LEFT] = value;
}
};
auto TRIGGER_RIGHT = ^(GCControllerButtonInput *button, float value, BOOL pressed) {
if (axis_value[(int)JoyAxis::TRIGGER_RIGHT] != value) {
axis_changed_mask |= (1 << (int)JoyAxis::TRIGGER_RIGHT);
axis_value[(int)JoyAxis::TRIGGER_RIGHT] = value;
}
};
if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) {
if (controller.physicalInputProfile != nil) {
GCPhysicalInputProfile *profile = controller.physicalInputProfile;
GCControllerButtonInput *buttonA = profile.buttons[GCInputButtonA];
GCControllerButtonInput *buttonB = profile.buttons[GCInputButtonB];
GCControllerButtonInput *buttonX = profile.buttons[GCInputButtonX];
GCControllerButtonInput *buttonY = profile.buttons[GCInputButtonY];
if (double_nintendo_joycon_layout) {
if (buttonA) {
buttonA.pressedChangedHandler = BUTTON(JoyButton::B);
}
if (buttonB) {
buttonB.pressedChangedHandler = BUTTON(JoyButton::A);
}
if (buttonX) {
buttonX.pressedChangedHandler = BUTTON(JoyButton::Y);
}
if (buttonY) {
buttonY.pressedChangedHandler = BUTTON(JoyButton::X);
}
} else if (single_nintendo_joycon_layout) {
if (buttonA) {
buttonA.pressedChangedHandler = BUTTON(JoyButton::A);
}
if (buttonB) {
buttonB.pressedChangedHandler = BUTTON(JoyButton::X);
}
if (buttonX) {
buttonX.pressedChangedHandler = BUTTON(JoyButton::B);
}
if (buttonY) {
buttonY.pressedChangedHandler = BUTTON(JoyButton::Y);
}
} else {
if (buttonA) {
buttonA.pressedChangedHandler = BUTTON(JoyButton::A);
}
if (buttonB) {
buttonB.pressedChangedHandler = BUTTON(JoyButton::B);
}
if (buttonX) {
buttonX.pressedChangedHandler = BUTTON(JoyButton::X);
}
if (buttonY) {
buttonY.pressedChangedHandler = BUTTON(JoyButton::Y);
}
}
GCControllerButtonInput *leftThumbstickButton = profile.buttons[GCInputLeftThumbstickButton];
GCControllerButtonInput *rightThumbstickButton = profile.buttons[GCInputRightThumbstickButton];
if (leftThumbstickButton) {
leftThumbstickButton.pressedChangedHandler = BUTTON(JoyButton::LEFT_STICK);
}
if (rightThumbstickButton) {
rightThumbstickButton.pressedChangedHandler = BUTTON(JoyButton::RIGHT_STICK);
}
GCControllerButtonInput *leftShoulder = profile.buttons[GCInputLeftShoulder];
GCControllerButtonInput *rightShoulder = profile.buttons[GCInputRightShoulder];
if (leftShoulder) {
leftShoulder.pressedChangedHandler = BUTTON(JoyButton::LEFT_SHOULDER);
}
if (rightShoulder) {
rightShoulder.pressedChangedHandler = BUTTON(JoyButton::RIGHT_SHOULDER);
}
GCControllerButtonInput *leftTrigger = profile.buttons[GCInputLeftTrigger];
GCControllerButtonInput *rightTrigger = profile.buttons[GCInputRightTrigger];
if (leftTrigger) {
leftTrigger.valueChangedHandler = TRIGGER_LEFT;
}
if (rightTrigger) {
rightTrigger.valueChangedHandler = TRIGGER_RIGHT;
}
GCControllerButtonInput *buttonMenu = profile.buttons[GCInputButtonMenu];
GCControllerButtonInput *buttonHome = profile.buttons[GCInputButtonHome];
GCControllerButtonInput *buttonOptions = profile.buttons[GCInputButtonOptions];
if (buttonMenu) {
buttonMenu.pressedChangedHandler = BUTTON(JoyButton::START);
}
if (buttonHome) {
buttonHome.pressedChangedHandler = BUTTON(JoyButton::GUIDE);
}
if (buttonOptions) {
buttonOptions.pressedChangedHandler = BUTTON(JoyButton::BACK);
}
// Xbox controller buttons.
if (@available(macOS 12.0, iOS 15.0, tvOS 15.0, *)) {
GCControllerButtonInput *buttonShare = profile.buttons[GCInputButtonShare];
if (buttonShare) {
buttonShare.pressedChangedHandler = BUTTON(JoyButton::MISC1);
}
}
GCControllerButtonInput *paddleButton1 = profile.buttons[GCInputXboxPaddleOne];
GCControllerButtonInput *paddleButton2 = profile.buttons[GCInputXboxPaddleTwo];
GCControllerButtonInput *paddleButton3 = profile.buttons[GCInputXboxPaddleThree];
GCControllerButtonInput *paddleButton4 = profile.buttons[GCInputXboxPaddleFour];
if (paddleButton1) {
paddleButton1.pressedChangedHandler = BUTTON(JoyButton::PADDLE1);
}
if (paddleButton2) {
paddleButton2.pressedChangedHandler = BUTTON(JoyButton::PADDLE2);
}
if (paddleButton3) {
paddleButton3.pressedChangedHandler = BUTTON(JoyButton::PADDLE3);
}
if (paddleButton4) {
paddleButton4.pressedChangedHandler = BUTTON(JoyButton::PADDLE4);
}
GCControllerDirectionPad *leftThumbstick = profile.dpads[GCInputLeftThumbstick];
if (leftThumbstick) {
leftThumbstick.valueChangedHandler = JOYSTICK_LEFT;
}
GCControllerDirectionPad *rightThumbstick = profile.dpads[GCInputRightThumbstick];
if (rightThumbstick) {
rightThumbstick.valueChangedHandler = JOYSTICK_RIGHT;
}
GCControllerDirectionPad *dpad = nil;
if (controller.extendedGamepad != nil) {
dpad = controller.extendedGamepad.dpad;
} else if (controller.microGamepad != nil) {
dpad = controller.microGamepad.dpad;
}
if (dpad) {
dpad.up.pressedChangedHandler = BUTTON(JoyButton::DPAD_UP);
dpad.down.pressedChangedHandler = BUTTON(JoyButton::DPAD_DOWN);
dpad.left.pressedChangedHandler = BUTTON(JoyButton::DPAD_LEFT);
dpad.right.pressedChangedHandler = BUTTON(JoyButton::DPAD_RIGHT);
}
}
} else if (controller.extendedGamepad != nil) {
GCExtendedGamepad *gamepad = controller.extendedGamepad;
if (double_nintendo_joycon_layout) {
gamepad.buttonA.pressedChangedHandler = BUTTON(JoyButton::B);
gamepad.buttonB.pressedChangedHandler = BUTTON(JoyButton::A);
gamepad.buttonX.pressedChangedHandler = BUTTON(JoyButton::Y);
gamepad.buttonY.pressedChangedHandler = BUTTON(JoyButton::X);
} else if (single_nintendo_joycon_layout) {
gamepad.buttonA.pressedChangedHandler = BUTTON(JoyButton::A);
gamepad.buttonB.pressedChangedHandler = BUTTON(JoyButton::X);
gamepad.buttonX.pressedChangedHandler = BUTTON(JoyButton::B);
gamepad.buttonY.pressedChangedHandler = BUTTON(JoyButton::Y);
} else {
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 = JOYSTICK_LEFT;
gamepad.rightThumbstick.valueChangedHandler = JOYSTICK_RIGHT;
gamepad.leftTrigger.valueChangedHandler = TRIGGER_LEFT;
gamepad.rightTrigger.valueChangedHandler = TRIGGER_RIGHT;
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.
char const *device_name;
if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)) {
device_name = p_controller.productCategory.UTF8String;
} else {
device_name = p_controller.vendorName.UTF8String;
}
Input::get_singleton()->joy_connection_changed(joy_id, true, String::utf8(device_name));
// 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() {
Input *input = Input::get_singleton();
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;
uint32_t changed = joypad.axis_changed_mask;
joypad.axis_changed_mask = 0;
// Loop over changed axes.
while (changed) {
// Find the index of the next set bit.
uint32_t i = (uint32_t)__builtin_ctzll(changed);
// Clear the set bit.
changed &= (changed - 1);
input->joy_axis(id, (JoyAxis)i, joypad.axis_value[i]);
}
if (joypad.force_feedback) {
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);
}
}
}
}
}
}

View file

@ -34,7 +34,6 @@
#import "apple_embedded.h"
#import "drivers/apple/joypad_apple.h"
#import "drivers/coreaudio/audio_driver_coreaudio.h"
#include "drivers/unix/os_unix.h"
#include "servers/audio/audio_server.h"
@ -48,6 +47,8 @@
#endif
#endif
class JoypadSDL;
class OS_AppleEmbedded : public OS_Unix {
private:
static HashMap<String, void *> dynamic_symbol_lookup_table;
@ -57,7 +58,9 @@ private:
AppleEmbedded *apple_embedded = nullptr;
JoypadApple *joypad_apple = nullptr;
#ifdef SDL_ENABLED
JoypadSDL *joypad_sdl = nullptr;
#endif
MainLoop *main_loop = nullptr;

View file

@ -43,6 +43,9 @@
#include "core/os/main_loop.h"
#include "core/profiling/profiling.h"
#import "drivers/apple/os_log_logger.h"
#ifdef SDL_ENABLED
#include "drivers/sdl/joypad_sdl.h"
#endif
#include "main/main.h"
#import <AVFoundation/AVFAudio.h>
@ -167,7 +170,14 @@ void OS_AppleEmbedded::initialize() {
}
void OS_AppleEmbedded::initialize_joypads() {
joypad_apple = memnew(JoypadApple);
#ifdef SDL_ENABLED
joypad_sdl = memnew(JoypadSDL());
if (joypad_sdl->initialize() != OK) {
ERR_PRINT("Couldn't initialize SDL joypad input driver.");
memdelete(joypad_sdl);
joypad_sdl = nullptr;
}
#endif
}
void OS_AppleEmbedded::initialize_modules() {
@ -176,9 +186,11 @@ void OS_AppleEmbedded::initialize_modules() {
}
void OS_AppleEmbedded::deinitialize_modules() {
if (joypad_apple) {
memdelete(joypad_apple);
#ifdef SDL_ENABLED
if (joypad_sdl) {
memdelete(joypad_sdl);
}
#endif
if (apple_embedded) {
memdelete(apple_embedded);
@ -214,7 +226,11 @@ bool OS_AppleEmbedded::iterate() {
DisplayServer::get_singleton()->process_events();
}
joypad_apple->process_joypads();
#ifdef SDL_ENABLED
if (joypad_sdl) {
joypad_sdl->process_events();
}
#endif
return Main::iteration();
}

View file

@ -188,6 +188,26 @@ if env["builtin_sdl"]:
"timer/windows/SDL_systimer.c",
]
elif env["platform"] in ["ios", "visionos"]:
if env["platform"] == "ios":
env_sdl.Append(CPPDEFINES=["SDL_PLATFORM_IOS"])
else:
env_sdl.Append(CPPDEFINES=["SDL_PLATFORM_VISIONOS"])
thirdparty_sources += [
"core/unix/SDL_appid.c",
"core/unix/SDL_poll.c",
"haptic/dummy/SDL_syshaptic.c",
"joystick/apple/SDL_mfijoystick.m",
"thread/pthread/SDL_syscond.c",
"thread/pthread/SDL_sysmutex.c",
"thread/pthread/SDL_sysrwlock.c",
"thread/pthread/SDL_syssem.c",
"thread/pthread/SDL_systhread.c",
"thread/pthread/SDL_systls.c",
"timer/unix/SDL_systimer.c",
]
thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources]
env_thirdparty = env_sdl.Clone()

View file

@ -117,7 +117,7 @@
#define SDL_INPUT_LINUXEV 1
#define SDL_THREAD_PTHREAD 1
// MacOS defines
// macOS defines
#elif defined(SDL_PLATFORM_MACOS)
#define SDL_PLATFORM_PRIVATE_NAME "macOS"
@ -132,6 +132,27 @@
#define SDL_THREAD_PTHREAD 1
#define SDL_THREAD_PTHREAD_RECURSIVE_MUTEX 1
// iOS/visionOS defines
#elif defined(SDL_PLATFORM_IOS) || defined(SDL_PLATFORM_VISIONOS)
#ifdef SDL_PLATFORM_IOS
#define SDL_PLATFORM_PRIVATE_NAME "iOS"
#else
#define SDL_PLATFORM_PRIVATE_NAME "visionOS"
#endif
#define SDL_PLATFORM_UNIX 1
#define HAVE_STDIO_H 1
#define HAVE_LIBC 1
#define SDL_JOYSTICK_MFI 1
#define SDL_HAPTIC_DUMMY 1
#define SDL_TIMER_UNIX 1
#define SDL_THREAD_PTHREAD 1
#define SDL_MAIN_HANDLED 1
// Other platforms are not supported (for now)
#else
#error "No SDL build config was found for this platform. Setup one before compiling the engine."

View file

@ -158,7 +158,7 @@ void JoypadSDL::process_events() {
joypads[joy_id].sdl_instance_idx = sdl_event.jdevice.which;
joypads[joy_id].supports_force_feedback = SDL_GetBooleanProperty(propertiesID, SDL_PROP_JOYSTICK_CAP_RUMBLE_BOOLEAN, false);
joypads[joy_id].guid = StringName(String(guid));
joypads[joy_id].supports_motion_sensors = SDL_GamepadHasSensor(gamepad, SDL_SENSOR_ACCEL) && SDL_GamepadHasSensor(gamepad, SDL_SENSOR_GYRO);
joypads[joy_id].supports_motion_sensors = SDL_GamepadHasSensor(gamepad, SDL_SENSOR_ACCEL) || SDL_GamepadHasSensor(gamepad, SDL_SENSOR_GYRO);
sdl_instance_id_to_joypad_id.insert(sdl_event.jdevice.which, joy_id);
@ -174,10 +174,12 @@ void JoypadSDL::process_events() {
joypad_info["serial_number"] = serial;
}
#if defined(WINDOWS_ENABLED) || defined(LINUXBSD_ENABLED) || defined(MACOS_ENABLED)
const uint64_t steam_handle = SDL_GetGamepadSteamHandle(gamepad);
if (steam_handle != 0) {
joypad_info["steam_input_index"] = itos(steam_handle);
}
#endif
#ifdef WINDOWS_ENABLED
const int player_index = SDL_GetJoystickPlayerIndex(joy);

View file

@ -183,3 +183,10 @@ def configure(env: "SConsEnvironment"):
"$APPLE_SDK_PATH/System/Library/Frameworks/OpenGLES.framework/Headers",
]
)
if env["sdl"]:
if env["builtin_sdl"]:
env.Append(CPPDEFINES=["SDL_ENABLED"])
else:
print_warning("`builtin_sdl` was explicitly disabled. Disabling SDL input driver support.")
env["sdl"] = False

View file

@ -162,3 +162,10 @@ def configure(env: "SConsEnvironment"):
if env["opengl3"]:
print_warning("The visionOS platform does not support the OpenGL rendering driver")
env["opengl3"] = False
if env["sdl"]:
if env["builtin_sdl"]:
env.Append(CPPDEFINES=["SDL_ENABLED"])
else:
print_warning("`builtin_sdl` was explicitly disabled. Disabling SDL input driver support.")
env["sdl"] = False

View file

@ -0,0 +1,151 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#if defined(SDL_HAPTIC_DUMMY) || defined(SDL_HAPTIC_DISABLED)
#include "../SDL_syshaptic.h"
static bool SDL_SYS_LogicError(void)
{
return SDL_SetError("Logic error: No haptic devices available.");
}
bool SDL_SYS_HapticInit(void)
{
return true;
}
int SDL_SYS_NumHaptics(void)
{
return 0;
}
SDL_HapticID SDL_SYS_HapticInstanceID(int index)
{
SDL_SYS_LogicError();
return 0;
}
const char *SDL_SYS_HapticName(int index)
{
SDL_SYS_LogicError();
return NULL;
}
bool SDL_SYS_HapticOpen(SDL_Haptic *haptic)
{
return SDL_SYS_LogicError();
}
int SDL_SYS_HapticMouse(void)
{
return -1;
}
bool SDL_SYS_JoystickIsHaptic(SDL_Joystick *joystick)
{
return false;
}
bool SDL_SYS_HapticOpenFromJoystick(SDL_Haptic *haptic, SDL_Joystick *joystick)
{
return SDL_SYS_LogicError();
}
bool SDL_SYS_JoystickSameHaptic(SDL_Haptic *haptic, SDL_Joystick *joystick)
{
return false;
}
void SDL_SYS_HapticClose(SDL_Haptic *haptic)
{
return;
}
void SDL_SYS_HapticQuit(void)
{
return;
}
bool SDL_SYS_HapticNewEffect(SDL_Haptic *haptic,
struct haptic_effect *effect, const SDL_HapticEffect *base)
{
return SDL_SYS_LogicError();
}
bool SDL_SYS_HapticUpdateEffect(SDL_Haptic *haptic,
struct haptic_effect *effect,
const SDL_HapticEffect *data)
{
return SDL_SYS_LogicError();
}
bool SDL_SYS_HapticRunEffect(SDL_Haptic *haptic, struct haptic_effect *effect,
Uint32 iterations)
{
return SDL_SYS_LogicError();
}
bool SDL_SYS_HapticStopEffect(SDL_Haptic *haptic, struct haptic_effect *effect)
{
return SDL_SYS_LogicError();
}
void SDL_SYS_HapticDestroyEffect(SDL_Haptic *haptic, struct haptic_effect *effect)
{
SDL_SYS_LogicError();
return;
}
int SDL_SYS_HapticGetEffectStatus(SDL_Haptic *haptic,
struct haptic_effect *effect)
{
SDL_SYS_LogicError();
return -1;
}
bool SDL_SYS_HapticSetGain(SDL_Haptic *haptic, int gain)
{
return SDL_SYS_LogicError();
}
bool SDL_SYS_HapticSetAutocenter(SDL_Haptic *haptic, int autocenter)
{
return SDL_SYS_LogicError();
}
bool SDL_SYS_HapticPause(SDL_Haptic *haptic)
{
return SDL_SYS_LogicError();
}
bool SDL_SYS_HapticResume(SDL_Haptic *haptic)
{
return SDL_SYS_LogicError();
}
bool SDL_SYS_HapticStopAll(SDL_Haptic *haptic)
{
return SDL_SYS_LogicError();
}
#endif // SDL_HAPTIC_DUMMY || SDL_HAPTIC_DISABLED

View file

@ -0,0 +1,45 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#if defined(SDL_LOADSO_DUMMY)
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
// System dependent library loading routines
SDL_SharedObject *SDL_LoadObject(const char *sofile)
{
SDL_Unsupported();
return NULL;
}
SDL_FunctionPointer SDL_LoadFunction(SDL_SharedObject *handle, const char *name)
{
SDL_Unsupported();
return NULL;
}
void SDL_UnloadObject(SDL_SharedObject *handle)
{
// no-op.
}
#endif // SDL_LOADSO_DUMMY

View file

@ -52,13 +52,13 @@ rm -f $target/core/windows/version.rc
rm -f $target/core/linux/SDL_{fcitx,ibus,ime,system_theme}.*
mkdir $target/haptic
cp -rv haptic/{*.{c,h},darwin,linux,windows} $target/haptic
cp -rv haptic/{*.{c,h},darwin,dummy,linux,windows} $target/haptic
mkdir $target/joystick
cp -rv joystick/{*.{c,h},apple,darwin,hidapi,linux,windows} $target/joystick
mkdir $target/loadso
cp -rv loadso/dlopen $target/loadso
cp -rv loadso/{dlopen,dummy} $target/loadso
mkdir $target/sensor
cp -rv sensor/{*.{c,h},dummy,windows} $target/sensor