Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/drivers/sdl/joypad_sdl.cpp
21019 views
1
/**************************************************************************/
2
/* joypad_sdl.cpp */
3
/**************************************************************************/
4
/* This file is part of: */
5
/* GODOT ENGINE */
6
/* https://godotengine.org */
7
/**************************************************************************/
8
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10
/* */
11
/* Permission is hereby granted, free of charge, to any person obtaining */
12
/* a copy of this software and associated documentation files (the */
13
/* "Software"), to deal in the Software without restriction, including */
14
/* without limitation the rights to use, copy, modify, merge, publish, */
15
/* distribute, sublicense, and/or sell copies of the Software, and to */
16
/* permit persons to whom the Software is furnished to do so, subject to */
17
/* the following conditions: */
18
/* */
19
/* The above copyright notice and this permission notice shall be */
20
/* included in all copies or substantial portions of the Software. */
21
/* */
22
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29
/**************************************************************************/
30
31
#include "joypad_sdl.h"
32
33
#ifdef SDL_ENABLED
34
35
#include "core/input/default_controller_mappings.h"
36
#include "core/os/time.h"
37
#include "core/variant/dictionary.h"
38
39
#include <SDL3/SDL.h>
40
#include <SDL3/SDL_error.h>
41
#include <SDL3/SDL_events.h>
42
#include <SDL3/SDL_gamepad.h>
43
#include <SDL3/SDL_iostream.h>
44
#include <SDL3/SDL_joystick.h>
45
46
// Macro to skip the SDL joystick event handling if the device is an SDL gamepad, because
47
// there are separate events for SDL gamepads
48
#define SKIP_EVENT_FOR_GAMEPAD \
49
if (SDL_IsGamepad(sdl_event.jdevice.which)) { \
50
continue; \
51
}
52
53
JoypadSDL::~JoypadSDL() {
54
// Process any remaining input events
55
process_events();
56
for (int i = 0; i < Input::JOYPADS_MAX; i++) {
57
if (joypads[i].attached) {
58
close_joypad(i);
59
}
60
}
61
SDL_Quit();
62
}
63
64
Error JoypadSDL::initialize() {
65
SDL_SetHint(SDL_HINT_JOYSTICK_THREAD, "1");
66
SDL_SetHint(SDL_HINT_NO_SIGNAL_HANDLERS, "1");
67
ERR_FAIL_COND_V_MSG(!SDL_Init(SDL_INIT_JOYSTICK | SDL_INIT_GAMEPAD), FAILED, SDL_GetError());
68
69
// Add Godot's mapping database from memory
70
int i = 0;
71
while (DefaultControllerMappings::mappings[i]) {
72
String mapping_string = DefaultControllerMappings::mappings[i++];
73
CharString data = mapping_string.utf8();
74
SDL_IOStream *rw = SDL_IOFromMem((void *)data.ptr(), data.size());
75
SDL_AddGamepadMappingsFromIO(rw, 1);
76
}
77
78
// Make sure that we handle already connected joypads when the driver is initialized.
79
process_events();
80
81
print_verbose("SDL: Init OK!");
82
return OK;
83
}
84
85
void JoypadSDL::process_events() {
86
// Update rumble first for it to be applied when we handle SDL events
87
for (int i = 0; i < Input::JOYPADS_MAX; i++) {
88
Joypad &joy = joypads[i];
89
if (joy.attached && joy.supports_force_feedback) {
90
uint64_t timestamp = Input::get_singleton()->get_joy_vibration_timestamp(i);
91
92
// Update the joypad rumble only if there was a new vibration request
93
if (timestamp > joy.ff_effect_timestamp) {
94
joy.ff_effect_timestamp = timestamp;
95
96
SDL_Joystick *sdl_joy = SDL_GetJoystickFromID(joypads[i].sdl_instance_idx);
97
Vector2 strength = Input::get_singleton()->get_joy_vibration_strength(i);
98
99
/*
100
If the vibration was requested to start, SDL_RumbleJoystick will start it.
101
If the vibration was requested to stop, strength and duration will be 0, so SDL will stop the rumble.
102
103
Here strength.y goes first and then strength.x, because Input.get_joy_vibration_strength().x
104
is vibration's weak magnitude (high frequency rumble), and .y is strong magnitude (low frequency rumble),
105
SDL_RumbleJoystick takes low frequency rumble first and then high frequency rumble.
106
*/
107
SDL_RumbleJoystick(
108
sdl_joy,
109
// Rumble strength goes from 0 to 0xFFFF
110
strength.y * UINT16_MAX,
111
strength.x * UINT16_MAX,
112
Input::get_singleton()->get_joy_vibration_duration(i) * 1000);
113
}
114
}
115
}
116
117
SDL_Event sdl_event;
118
while (SDL_PollEvent(&sdl_event)) {
119
// A new joypad was attached
120
if (sdl_event.type == SDL_EVENT_JOYSTICK_ADDED) {
121
int joy_id = Input::get_singleton()->get_unused_joy_id();
122
if (joy_id == -1) {
123
// There is no space for more joypads...
124
print_error("A new joypad was attached but couldn't allocate a new id for it because joypad limit was reached.");
125
} else {
126
SDL_Joystick *joy = nullptr;
127
SDL_Gamepad *gamepad = nullptr;
128
String device_name;
129
130
// Gamepads must be opened with SDL_OpenGamepad to get their special remapped events
131
if (SDL_IsGamepad(sdl_event.jdevice.which)) {
132
gamepad = SDL_OpenGamepad(sdl_event.jdevice.which);
133
134
ERR_CONTINUE_MSG(!gamepad,
135
vformat("Error opening gamepad at index %d: %s", sdl_event.jdevice.which, SDL_GetError()));
136
137
device_name = SDL_GetGamepadName(gamepad);
138
joy = SDL_GetGamepadJoystick(gamepad);
139
140
print_verbose(vformat("SDL: Gamepad %s connected", SDL_GetGamepadName(gamepad)));
141
} else {
142
joy = SDL_OpenJoystick(sdl_event.jdevice.which);
143
ERR_CONTINUE_MSG(!joy,
144
vformat("Error opening joystick at index %d: %s", sdl_event.jdevice.which, SDL_GetError()));
145
146
device_name = SDL_GetJoystickName(joy);
147
148
print_verbose(vformat("SDL: Joystick %s connected", SDL_GetJoystickName(joy)));
149
}
150
151
const int MAX_GUID_SIZE = 64;
152
char guid[MAX_GUID_SIZE] = {};
153
SDL_GUID joy_guid = SDL_GetJoystickGUID(joy);
154
SDL_GUIDToString(joy_guid, guid, MAX_GUID_SIZE);
155
SDL_PropertiesID propertiesID = SDL_GetJoystickProperties(joy);
156
157
joypads[joy_id].attached = true;
158
joypads[joy_id].sdl_instance_idx = sdl_event.jdevice.which;
159
joypads[joy_id].supports_force_feedback = SDL_GetBooleanProperty(propertiesID, SDL_PROP_JOYSTICK_CAP_RUMBLE_BOOLEAN, false);
160
joypads[joy_id].guid = StringName(String(guid));
161
joypads[joy_id].supports_motion_sensors = SDL_GamepadHasSensor(gamepad, SDL_SENSOR_ACCEL) && SDL_GamepadHasSensor(gamepad, SDL_SENSOR_GYRO);
162
163
sdl_instance_id_to_joypad_id.insert(sdl_event.jdevice.which, joy_id);
164
165
Dictionary joypad_info;
166
// Skip Godot's mapping system if SDL already handles the joypad's mapping.
167
joypad_info["mapping_handled"] = SDL_IsGamepad(sdl_event.jdevice.which);
168
joypad_info["raw_name"] = String::utf8(SDL_GetJoystickName(joy));
169
joypad_info["vendor_id"] = itos(SDL_GetJoystickVendor(joy));
170
joypad_info["product_id"] = itos(SDL_GetJoystickProduct(joy));
171
172
const String serial = String(SDL_GetJoystickSerial(joy));
173
if (!serial.is_empty()) {
174
joypad_info["serial_number"] = serial;
175
}
176
177
const uint64_t steam_handle = SDL_GetGamepadSteamHandle(gamepad);
178
if (steam_handle != 0) {
179
joypad_info["steam_input_index"] = itos(steam_handle);
180
}
181
182
#ifdef WINDOWS_ENABLED
183
const int player_index = SDL_GetJoystickPlayerIndex(joy);
184
if (player_index >= 0 && joy_guid.data[14] == 'x') { // See also "SDL_IsJoystickXInput" in "thirdparty/sdl/joystick/SDL_joystick.c".
185
// For XInput controllers SDL_GetJoystickPlayerIndex returns the XInput user index.
186
joypad_info["xinput_index"] = itos(player_index);
187
}
188
#endif
189
190
Input::get_singleton()->joy_connection_changed(
191
joy_id,
192
true,
193
device_name,
194
joypads[joy_id].guid,
195
joypad_info);
196
197
Input::get_singleton()->set_joy_features(joy_id, &joypads[joy_id]);
198
199
if (joypads[joy_id].supports_motion_sensors) {
200
// Data rate for all sensors should be the same.
201
Input::get_singleton()->set_joy_motion_sensors_rate(joy_id, SDL_GetGamepadSensorDataRate(gamepad, SDL_SENSOR_ACCEL));
202
}
203
}
204
// An event for an attached joypad
205
} else if (sdl_event.type >= SDL_EVENT_JOYSTICK_AXIS_MOTION && sdl_event.type < SDL_EVENT_FINGER_DOWN && sdl_instance_id_to_joypad_id.has(sdl_event.jdevice.which)) {
206
int joy_id = sdl_instance_id_to_joypad_id.get(sdl_event.jdevice.which);
207
208
switch (sdl_event.type) {
209
case SDL_EVENT_JOYSTICK_REMOVED:
210
Input::get_singleton()->joy_connection_changed(joy_id, false, "");
211
close_joypad(joy_id);
212
break;
213
214
case SDL_EVENT_JOYSTICK_AXIS_MOTION:
215
SKIP_EVENT_FOR_GAMEPAD;
216
217
Input::get_singleton()->joy_axis(
218
joy_id,
219
static_cast<JoyAxis>(sdl_event.jaxis.axis), // Godot joy axis constants are already intentionally the same as SDL's
220
((sdl_event.jaxis.value - SDL_JOYSTICK_AXIS_MIN) / (float)(SDL_JOYSTICK_AXIS_MAX - SDL_JOYSTICK_AXIS_MIN) - 0.5f) * 2.0f);
221
break;
222
223
case SDL_EVENT_JOYSTICK_BUTTON_UP:
224
case SDL_EVENT_JOYSTICK_BUTTON_DOWN:
225
SKIP_EVENT_FOR_GAMEPAD;
226
227
// Some devices report pressing buttons with indices like 232+, 241+, etc. that are not valid,
228
// so we ignore them here.
229
if (sdl_event.jbutton.button >= (int)JoyButton::MAX) {
230
continue;
231
}
232
233
Input::get_singleton()->joy_button(
234
joy_id,
235
static_cast<JoyButton>(sdl_event.jbutton.button), // Godot button constants are intentionally the same as SDL's, so we can just straight up use them
236
sdl_event.jbutton.down);
237
break;
238
239
case SDL_EVENT_JOYSTICK_HAT_MOTION:
240
SKIP_EVENT_FOR_GAMEPAD;
241
242
Input::get_singleton()->joy_hat(
243
joy_id,
244
(HatMask)sdl_event.jhat.value // Godot hat masks are identical to SDL hat masks, so we can just use them as-is.
245
);
246
break;
247
248
case SDL_EVENT_GAMEPAD_AXIS_MOTION: {
249
float axis_value;
250
251
if (sdl_event.gaxis.axis == SDL_GAMEPAD_AXIS_LEFT_TRIGGER || sdl_event.gaxis.axis == SDL_GAMEPAD_AXIS_RIGHT_TRIGGER) {
252
// Gamepad triggers go from 0 to SDL_JOYSTICK_AXIS_MAX
253
axis_value = sdl_event.gaxis.value / (float)SDL_JOYSTICK_AXIS_MAX;
254
} else {
255
// Other axis go from SDL_JOYSTICK_AXIS_MIN to SDL_JOYSTICK_AXIS_MAX
256
axis_value =
257
((sdl_event.gaxis.value - SDL_JOYSTICK_AXIS_MIN) / (float)(SDL_JOYSTICK_AXIS_MAX - SDL_JOYSTICK_AXIS_MIN) - 0.5f) * 2.0f;
258
}
259
260
Input::get_singleton()->joy_axis(
261
joy_id,
262
static_cast<JoyAxis>(sdl_event.gaxis.axis), // Godot joy axis constants are already intentionally the same as SDL's
263
axis_value);
264
} break;
265
266
// Do note SDL gamepads do not have separate events for the dpad
267
case SDL_EVENT_GAMEPAD_BUTTON_UP:
268
case SDL_EVENT_GAMEPAD_BUTTON_DOWN:
269
Input::get_singleton()->joy_button(
270
joy_id,
271
static_cast<JoyButton>(sdl_event.gbutton.button), // Godot button constants are intentionally the same as SDL's, so we can just straight up use them
272
sdl_event.gbutton.down);
273
break;
274
}
275
}
276
}
277
278
for (int i = 0; i < Input::JOYPADS_MAX; i++) {
279
Joypad &joy = joypads[i];
280
if (!joy.attached || !joy.supports_motion_sensors) {
281
continue;
282
}
283
SDL_Gamepad *gamepad = SDL_GetGamepadFromID(joy.sdl_instance_idx);
284
// gamepad should not be NULL since joy.supports_motion_sensors is true here.
285
286
float accel_data[3];
287
float gyro_data[3];
288
SDL_GetGamepadSensorData(gamepad, SDL_SENSOR_ACCEL, accel_data, 3);
289
SDL_GetGamepadSensorData(gamepad, SDL_SENSOR_GYRO, gyro_data, 3);
290
291
Input::get_singleton()->joy_motion_sensors(
292
i,
293
Vector3(-accel_data[0], -accel_data[1], -accel_data[2]),
294
Vector3(gyro_data[0], gyro_data[1], gyro_data[2]));
295
}
296
}
297
298
void JoypadSDL::close_joypad(int p_pad_idx) {
299
int sdl_instance_idx = joypads[p_pad_idx].sdl_instance_idx;
300
301
joypads[p_pad_idx].attached = false;
302
sdl_instance_id_to_joypad_id.erase(sdl_instance_idx);
303
304
if (SDL_IsGamepad(sdl_instance_idx)) {
305
SDL_Gamepad *gamepad = SDL_GetGamepadFromID(sdl_instance_idx);
306
SDL_CloseGamepad(gamepad);
307
} else {
308
SDL_Joystick *joy = SDL_GetJoystickFromID(sdl_instance_idx);
309
SDL_CloseJoystick(joy);
310
}
311
}
312
313
bool JoypadSDL::Joypad::has_joy_light() const {
314
SDL_Joystick *joystick = get_sdl_joystick();
315
SDL_PropertiesID properties_id = SDL_GetJoystickProperties(joystick);
316
if (properties_id == 0) {
317
return false;
318
}
319
return SDL_GetBooleanProperty(properties_id, SDL_PROP_JOYSTICK_CAP_RGB_LED_BOOLEAN, false) || SDL_GetBooleanProperty(properties_id, SDL_PROP_JOYSTICK_CAP_MONO_LED_BOOLEAN, false);
320
}
321
322
void JoypadSDL::Joypad::set_joy_light(const Color &p_color) {
323
SDL_SetJoystickLED(get_sdl_joystick(), p_color.get_r8(), p_color.get_g8(), p_color.get_b8());
324
}
325
326
bool JoypadSDL::Joypad::has_joy_motion_sensors() const {
327
return supports_motion_sensors;
328
}
329
330
void JoypadSDL::Joypad::set_joy_motion_sensors_enabled(bool p_enable) {
331
SDL_Gamepad *gamepad = get_sdl_gamepad();
332
SDL_SetGamepadSensorEnabled(gamepad, SDL_SENSOR_ACCEL, p_enable);
333
SDL_SetGamepadSensorEnabled(gamepad, SDL_SENSOR_GYRO, p_enable);
334
}
335
336
SDL_Joystick *JoypadSDL::Joypad::get_sdl_joystick() const {
337
return SDL_GetJoystickFromID(sdl_instance_idx);
338
}
339
340
SDL_Gamepad *JoypadSDL::Joypad::get_sdl_gamepad() const {
341
return SDL_GetGamepadFromID(sdl_instance_idx);
342
}
343
344
#endif // SDL_ENABLED
345
346