Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/drivers/sdl/joypad_sdl.cpp
11351 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
JoypadSDL *JoypadSDL::singleton = nullptr;
47
48
// Macro to skip the SDL joystick event handling if the device is an SDL gamepad, because
49
// there are separate events for SDL gamepads
50
#define SKIP_EVENT_FOR_GAMEPAD \
51
if (SDL_IsGamepad(sdl_event.jdevice.which)) { \
52
continue; \
53
}
54
55
JoypadSDL::JoypadSDL() {
56
singleton = this;
57
}
58
59
#ifdef WINDOWS_ENABLED
60
extern "C" {
61
HWND SDL_HelperWindow;
62
}
63
64
// Required for DInput joypads to work
65
// TODO: remove this workaround when we update to newer version of SDL
66
JoypadSDL::JoypadSDL(HWND p_helper_window) :
67
JoypadSDL() {
68
SDL_HelperWindow = p_helper_window;
69
}
70
#endif
71
72
JoypadSDL::~JoypadSDL() {
73
// Process any remaining input events
74
process_events();
75
for (int i = 0; i < Input::JOYPADS_MAX; i++) {
76
if (joypads[i].attached) {
77
close_joypad(i);
78
}
79
}
80
SDL_Quit();
81
singleton = nullptr;
82
}
83
84
JoypadSDL *JoypadSDL::get_singleton() {
85
return singleton;
86
}
87
88
Error JoypadSDL::initialize() {
89
SDL_SetHint(SDL_HINT_JOYSTICK_THREAD, "1");
90
SDL_SetHint(SDL_HINT_NO_SIGNAL_HANDLERS, "1");
91
ERR_FAIL_COND_V_MSG(!SDL_Init(SDL_INIT_JOYSTICK | SDL_INIT_GAMEPAD), FAILED, SDL_GetError());
92
93
// Add Godot's mapping database from memory
94
int i = 0;
95
while (DefaultControllerMappings::mappings[i]) {
96
String mapping_string = DefaultControllerMappings::mappings[i++];
97
CharString data = mapping_string.utf8();
98
SDL_IOStream *rw = SDL_IOFromMem((void *)data.ptr(), data.size());
99
SDL_AddGamepadMappingsFromIO(rw, 1);
100
}
101
102
// Make sure that we handle already connected joypads when the driver is initialized.
103
process_events();
104
105
print_verbose("SDL: Init OK!");
106
return OK;
107
}
108
109
void JoypadSDL::process_events() {
110
// Update rumble first for it to be applied when we handle SDL events
111
for (int i = 0; i < Input::JOYPADS_MAX; i++) {
112
Joypad &joy = joypads[i];
113
if (joy.attached && joy.supports_force_feedback) {
114
uint64_t timestamp = Input::get_singleton()->get_joy_vibration_timestamp(i);
115
116
// Update the joypad rumble only if there was a new vibration request
117
if (timestamp > joy.ff_effect_timestamp) {
118
joy.ff_effect_timestamp = timestamp;
119
120
SDL_Joystick *sdl_joy = SDL_GetJoystickFromID(joypads[i].sdl_instance_idx);
121
Vector2 strength = Input::get_singleton()->get_joy_vibration_strength(i);
122
123
/*
124
If the vibration was requested to start, SDL_RumbleJoystick will start it.
125
If the vibration was requested to stop, strength and duration will be 0, so SDL will stop the rumble.
126
127
Here strength.y goes first and then strength.x, because Input.get_joy_vibration_strength().x
128
is vibration's weak magnitude (high frequency rumble), and .y is strong magnitude (low frequency rumble),
129
SDL_RumbleJoystick takes low frequency rumble first and then high frequency rumble.
130
*/
131
SDL_RumbleJoystick(
132
sdl_joy,
133
// Rumble strength goes from 0 to 0xFFFF
134
strength.y * UINT16_MAX,
135
strength.x * UINT16_MAX,
136
Input::get_singleton()->get_joy_vibration_duration(i) * 1000);
137
}
138
}
139
}
140
141
SDL_Event sdl_event;
142
while (SDL_PollEvent(&sdl_event)) {
143
// A new joypad was attached
144
if (sdl_event.type == SDL_EVENT_JOYSTICK_ADDED) {
145
int joy_id = Input::get_singleton()->get_unused_joy_id();
146
if (joy_id == -1) {
147
// There is no space for more joypads...
148
print_error("A new joypad was attached but couldn't allocate a new id for it because joypad limit was reached.");
149
} else {
150
SDL_Joystick *joy = nullptr;
151
SDL_Gamepad *gamepad = nullptr;
152
String device_name;
153
154
// Gamepads must be opened with SDL_OpenGamepad to get their special remapped events
155
if (SDL_IsGamepad(sdl_event.jdevice.which)) {
156
gamepad = SDL_OpenGamepad(sdl_event.jdevice.which);
157
158
ERR_CONTINUE_MSG(!gamepad,
159
vformat("Error opening gamepad at index %d: %s", sdl_event.jdevice.which, SDL_GetError()));
160
161
device_name = SDL_GetGamepadName(gamepad);
162
joy = SDL_GetGamepadJoystick(gamepad);
163
164
print_verbose(vformat("SDL: Gamepad %s connected", SDL_GetGamepadName(gamepad)));
165
} else {
166
joy = SDL_OpenJoystick(sdl_event.jdevice.which);
167
ERR_CONTINUE_MSG(!joy,
168
vformat("Error opening joystick at index %d: %s", sdl_event.jdevice.which, SDL_GetError()));
169
170
device_name = SDL_GetJoystickName(joy);
171
172
print_verbose(vformat("SDL: Joystick %s connected", SDL_GetJoystickName(joy)));
173
}
174
175
const int MAX_GUID_SIZE = 64;
176
char guid[MAX_GUID_SIZE] = {};
177
178
SDL_GUIDToString(SDL_GetJoystickGUID(joy), guid, MAX_GUID_SIZE);
179
SDL_PropertiesID propertiesID = SDL_GetJoystickProperties(joy);
180
181
joypads[joy_id].attached = true;
182
joypads[joy_id].sdl_instance_idx = sdl_event.jdevice.which;
183
joypads[joy_id].supports_force_feedback = SDL_GetBooleanProperty(propertiesID, SDL_PROP_JOYSTICK_CAP_RUMBLE_BOOLEAN, false);
184
joypads[joy_id].guid = StringName(String(guid));
185
186
sdl_instance_id_to_joypad_id.insert(sdl_event.jdevice.which, joy_id);
187
188
Dictionary joypad_info;
189
joypad_info["mapping_handled"] = true; // Skip Godot's mapping system because SDL already handles the joypad's mapping.
190
joypad_info["raw_name"] = String(SDL_GetJoystickName(joy));
191
joypad_info["vendor_id"] = itos(SDL_GetJoystickVendor(joy));
192
joypad_info["product_id"] = itos(SDL_GetJoystickProduct(joy));
193
194
const uint64_t steam_handle = SDL_GetGamepadSteamHandle(gamepad);
195
if (steam_handle != 0) {
196
joypad_info["steam_input_index"] = itos(steam_handle);
197
}
198
199
const int player_index = SDL_GetJoystickPlayerIndex(joy);
200
if (player_index >= 0) {
201
// For XInput controllers SDL_GetJoystickPlayerIndex returns the XInput user index.
202
joypad_info["xinput_index"] = itos(player_index);
203
}
204
205
Input::get_singleton()->joy_connection_changed(
206
joy_id,
207
true,
208
device_name,
209
joypads[joy_id].guid,
210
joypad_info);
211
}
212
// An event for an attached joypad
213
} 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)) {
214
int joy_id = sdl_instance_id_to_joypad_id.get(sdl_event.jdevice.which);
215
216
switch (sdl_event.type) {
217
case SDL_EVENT_JOYSTICK_REMOVED:
218
Input::get_singleton()->joy_connection_changed(joy_id, false, "");
219
close_joypad(joy_id);
220
break;
221
222
case SDL_EVENT_JOYSTICK_AXIS_MOTION:
223
SKIP_EVENT_FOR_GAMEPAD;
224
225
Input::get_singleton()->joy_axis(
226
joy_id,
227
static_cast<JoyAxis>(sdl_event.jaxis.axis), // Godot joy axis constants are already intentionally the same as SDL's
228
((sdl_event.jaxis.value - SDL_JOYSTICK_AXIS_MIN) / (float)(SDL_JOYSTICK_AXIS_MAX - SDL_JOYSTICK_AXIS_MIN) - 0.5f) * 2.0f);
229
break;
230
231
case SDL_EVENT_JOYSTICK_BUTTON_UP:
232
case SDL_EVENT_JOYSTICK_BUTTON_DOWN:
233
SKIP_EVENT_FOR_GAMEPAD;
234
235
Input::get_singleton()->joy_button(
236
joy_id,
237
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
238
sdl_event.jbutton.down);
239
break;
240
241
case SDL_EVENT_JOYSTICK_HAT_MOTION:
242
SKIP_EVENT_FOR_GAMEPAD;
243
244
Input::get_singleton()->joy_hat(
245
joy_id,
246
(HatMask)sdl_event.jhat.value // Godot hat masks are identical to SDL hat masks, so we can just use them as-is.
247
);
248
break;
249
250
case SDL_EVENT_GAMEPAD_AXIS_MOTION: {
251
float axis_value;
252
253
if (sdl_event.gaxis.axis == SDL_GAMEPAD_AXIS_LEFT_TRIGGER || sdl_event.gaxis.axis == SDL_GAMEPAD_AXIS_RIGHT_TRIGGER) {
254
// Gamepad triggers go from 0 to SDL_JOYSTICK_AXIS_MAX
255
axis_value = sdl_event.gaxis.value / (float)SDL_JOYSTICK_AXIS_MAX;
256
} else {
257
// Other axis go from SDL_JOYSTICK_AXIS_MIN to SDL_JOYSTICK_AXIS_MAX
258
axis_value =
259
((sdl_event.gaxis.value - SDL_JOYSTICK_AXIS_MIN) / (float)(SDL_JOYSTICK_AXIS_MAX - SDL_JOYSTICK_AXIS_MIN) - 0.5f) * 2.0f;
260
}
261
262
Input::get_singleton()->joy_axis(
263
joy_id,
264
static_cast<JoyAxis>(sdl_event.gaxis.axis), // Godot joy axis constants are already intentionally the same as SDL's
265
axis_value);
266
} break;
267
268
// Do note SDL gamepads do not have separate events for the dpad
269
case SDL_EVENT_GAMEPAD_BUTTON_UP:
270
case SDL_EVENT_GAMEPAD_BUTTON_DOWN:
271
Input::get_singleton()->joy_button(
272
joy_id,
273
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
274
sdl_event.gbutton.down);
275
break;
276
}
277
}
278
}
279
}
280
281
void JoypadSDL::close_joypad(int p_pad_idx) {
282
int sdl_instance_idx = joypads[p_pad_idx].sdl_instance_idx;
283
284
joypads[p_pad_idx].attached = false;
285
sdl_instance_id_to_joypad_id.erase(sdl_instance_idx);
286
287
if (SDL_IsGamepad(sdl_instance_idx)) {
288
SDL_Gamepad *gamepad = SDL_GetGamepadFromID(sdl_instance_idx);
289
SDL_CloseGamepad(gamepad);
290
} else {
291
SDL_Joystick *joy = SDL_GetJoystickFromID(sdl_instance_idx);
292
SDL_CloseJoystick(joy);
293
}
294
}
295
296
#endif // SDL_ENABLED
297
298