1/*
2 Simple DirectMedia Layer
3 Copyright (C) 2023 Max Maisel <max.maisel@posteo.de>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#ifdef SDL_JOYSTICK_HIDAPI
24
25#include "../SDL_sysjoystick.h"
26#include "SDL_hidapijoystick_c.h"
27
28#ifdef SDL_JOYSTICK_HIDAPI_STEAMDECK
29
30/*****************************************************************************************************/
31
32#include "steam/controller_constants.h"
33#include "steam/controller_structs.h"
34
35enum
36{
37 SDL_GAMEPAD_BUTTON_STEAM_DECK_QAM = 11,
38 SDL_GAMEPAD_BUTTON_STEAM_DECK_RIGHT_PADDLE1,
39 SDL_GAMEPAD_BUTTON_STEAM_DECK_LEFT_PADDLE1,
40 SDL_GAMEPAD_BUTTON_STEAM_DECK_RIGHT_PADDLE2,
41 SDL_GAMEPAD_BUTTON_STEAM_DECK_LEFT_PADDLE2,
42 SDL_GAMEPAD_NUM_STEAM_DECK_BUTTONS,
43};
44
45typedef enum
46{
47 STEAMDECK_LBUTTON_R2 = 0x00000001,
48 STEAMDECK_LBUTTON_L2 = 0x00000002,
49 STEAMDECK_LBUTTON_R = 0x00000004,
50 STEAMDECK_LBUTTON_L = 0x00000008,
51 STEAMDECK_LBUTTON_Y = 0x00000010,
52 STEAMDECK_LBUTTON_B = 0x00000020,
53 STEAMDECK_LBUTTON_X = 0x00000040,
54 STEAMDECK_LBUTTON_A = 0x00000080,
55 STEAMDECK_LBUTTON_DPAD_UP = 0x00000100,
56 STEAMDECK_LBUTTON_DPAD_RIGHT = 0x00000200,
57 STEAMDECK_LBUTTON_DPAD_LEFT = 0x00000400,
58 STEAMDECK_LBUTTON_DPAD_DOWN = 0x00000800,
59 STEAMDECK_LBUTTON_VIEW = 0x00001000,
60 STEAMDECK_LBUTTON_STEAM = 0x00002000,
61 STEAMDECK_LBUTTON_MENU = 0x00004000,
62 STEAMDECK_LBUTTON_L5 = 0x00008000,
63 STEAMDECK_LBUTTON_R5 = 0x00010000,
64 STEAMDECK_LBUTTON_LEFT_PAD = 0x00020000,
65 STEAMDECK_LBUTTON_RIGHT_PAD = 0x00040000,
66 STEAMDECK_LBUTTON_L3 = 0x00400000,
67 STEAMDECK_LBUTTON_R3 = 0x04000000,
68
69 STEAMDECK_HBUTTON_L4 = 0x00000200,
70 STEAMDECK_HBUTTON_R4 = 0x00000400,
71 STEAMDECK_HBUTTON_QAM = 0x00040000,
72} SteamDeckButtons;
73
74typedef struct
75{
76 Uint32 update_rate_us;
77 Uint32 sensor_timestamp_us;
78 Uint64 last_button_state;
79 Uint8 watchdog_counter;
80} SDL_DriverSteamDeck_Context;
81
82static bool DisableDeckLizardMode(SDL_hid_device *dev)
83{
84 int rc;
85 Uint8 buffer[HID_FEATURE_REPORT_BYTES + 1] = { 0 };
86 FeatureReportMsg *msg = (FeatureReportMsg *)(buffer + 1);
87
88 msg->header.type = ID_CLEAR_DIGITAL_MAPPINGS;
89
90 rc = SDL_hid_send_feature_report(dev, buffer, sizeof(buffer));
91 if (rc != sizeof(buffer))
92 return false;
93
94 msg->header.type = ID_SET_SETTINGS_VALUES;
95 msg->header.length = 5 * sizeof(ControllerSetting);
96 msg->payload.setSettingsValues.settings[0].settingNum = SETTING_SMOOTH_ABSOLUTE_MOUSE;
97 msg->payload.setSettingsValues.settings[0].settingValue = 0;
98 msg->payload.setSettingsValues.settings[1].settingNum = SETTING_LEFT_TRACKPAD_MODE;
99 msg->payload.setSettingsValues.settings[1].settingValue = TRACKPAD_NONE;
100 msg->payload.setSettingsValues.settings[2].settingNum = SETTING_RIGHT_TRACKPAD_MODE; // disable mouse
101 msg->payload.setSettingsValues.settings[2].settingValue = TRACKPAD_NONE;
102 msg->payload.setSettingsValues.settings[3].settingNum = SETTING_LEFT_TRACKPAD_CLICK_PRESSURE; // disable clicky pad
103 msg->payload.setSettingsValues.settings[3].settingValue = 0xFFFF;
104 msg->payload.setSettingsValues.settings[4].settingNum = SETTING_RIGHT_TRACKPAD_CLICK_PRESSURE; // disable clicky pad
105 msg->payload.setSettingsValues.settings[4].settingValue = 0xFFFF;
106
107 rc = SDL_hid_send_feature_report(dev, buffer, sizeof(buffer));
108 if (rc != sizeof(buffer))
109 return false;
110
111 // There may be a lingering report read back after changing settings.
112 // Discard it.
113 SDL_hid_get_feature_report(dev, buffer, sizeof(buffer));
114
115 return true;
116}
117
118static bool FeedDeckLizardWatchdog(SDL_hid_device *dev)
119{
120 int rc;
121 Uint8 buffer[HID_FEATURE_REPORT_BYTES + 1] = { 0 };
122 FeatureReportMsg *msg = (FeatureReportMsg *)(buffer + 1);
123
124 msg->header.type = ID_CLEAR_DIGITAL_MAPPINGS;
125
126 rc = SDL_hid_send_feature_report(dev, buffer, sizeof(buffer));
127 if (rc != sizeof(buffer))
128 return false;
129
130 msg->header.type = ID_SET_SETTINGS_VALUES;
131 msg->header.length = 1 * sizeof(ControllerSetting);
132 msg->payload.setSettingsValues.settings[0].settingNum = SETTING_RIGHT_TRACKPAD_MODE;
133 msg->payload.setSettingsValues.settings[0].settingValue = TRACKPAD_NONE;
134
135 rc = SDL_hid_send_feature_report(dev, buffer, sizeof(buffer));
136 if (rc != sizeof(buffer))
137 return false;
138
139 // There may be a lingering report read back after changing settings.
140 // Discard it.
141 SDL_hid_get_feature_report(dev, buffer, sizeof(buffer));
142
143 return true;
144}
145
146static void HIDAPI_DriverSteamDeck_HandleState(SDL_HIDAPI_Device *device,
147 SDL_Joystick *joystick,
148 ValveInReport_t *pInReport)
149{
150 float values[3];
151 SDL_DriverSteamDeck_Context *ctx = (SDL_DriverSteamDeck_Context *)device->context;
152 Uint64 timestamp = SDL_GetTicksNS();
153
154 if (pInReport->payload.deckState.ulButtons != ctx->last_button_state) {
155 Uint8 hat = 0;
156
157 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH,
158 ((pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_A) != 0));
159 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST,
160 ((pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_B) != 0));
161 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST,
162 ((pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_X) != 0));
163 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH,
164 ((pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_Y) != 0));
165
166 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER,
167 ((pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_L) != 0));
168 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER,
169 ((pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_R) != 0));
170
171 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK,
172 ((pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_VIEW) != 0));
173 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START,
174 ((pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_MENU) != 0));
175 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE,
176 ((pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_STEAM) != 0));
177 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_STEAM_DECK_QAM,
178 ((pInReport->payload.deckState.ulButtonsH & STEAMDECK_HBUTTON_QAM) != 0));
179
180 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK,
181 ((pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_L3) != 0));
182 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK,
183 ((pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_R3) != 0));
184
185 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_STEAM_DECK_RIGHT_PADDLE1,
186 ((pInReport->payload.deckState.ulButtonsH & STEAMDECK_HBUTTON_R4) != 0));
187 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_STEAM_DECK_LEFT_PADDLE1,
188 ((pInReport->payload.deckState.ulButtonsH & STEAMDECK_HBUTTON_L4) != 0));
189 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_STEAM_DECK_RIGHT_PADDLE2,
190 ((pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_R5) != 0));
191 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_STEAM_DECK_LEFT_PADDLE2,
192 ((pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_L5) != 0));
193
194 if (pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_DPAD_UP) {
195 hat |= SDL_HAT_UP;
196 }
197 if (pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_DPAD_DOWN) {
198 hat |= SDL_HAT_DOWN;
199 }
200 if (pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_DPAD_LEFT) {
201 hat |= SDL_HAT_LEFT;
202 }
203 if (pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_DPAD_RIGHT) {
204 hat |= SDL_HAT_RIGHT;
205 }
206 SDL_SendJoystickHat(timestamp, joystick, 0, hat);
207
208 ctx->last_button_state = pInReport->payload.deckState.ulButtons;
209 }
210
211 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER,
212 (int)pInReport->payload.deckState.sTriggerRawL * 2 - 32768);
213 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER,
214 (int)pInReport->payload.deckState.sTriggerRawR * 2 - 32768);
215
216 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX,
217 pInReport->payload.deckState.sLeftStickX);
218 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY,
219 -pInReport->payload.deckState.sLeftStickY);
220 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX,
221 pInReport->payload.deckState.sRightStickX);
222 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY,
223 -pInReport->payload.deckState.sRightStickY);
224
225 ctx->sensor_timestamp_us += ctx->update_rate_us;
226
227 values[0] = (pInReport->payload.deckState.sGyroX / 32768.0f) * (2000.0f * (SDL_PI_F / 180.0f));
228 values[1] = (pInReport->payload.deckState.sGyroZ / 32768.0f) * (2000.0f * (SDL_PI_F / 180.0f));
229 values[2] = (-pInReport->payload.deckState.sGyroY / 32768.0f) * (2000.0f * (SDL_PI_F / 180.0f));
230 SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO, ctx->sensor_timestamp_us, values, 3);
231
232 values[0] = (pInReport->payload.deckState.sAccelX / 32768.0f) * 2.0f * SDL_STANDARD_GRAVITY;
233 values[1] = (pInReport->payload.deckState.sAccelZ / 32768.0f) * 2.0f * SDL_STANDARD_GRAVITY;
234 values[2] = (-pInReport->payload.deckState.sAccelY / 32768.0f) * 2.0f * SDL_STANDARD_GRAVITY;
235 SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, ctx->sensor_timestamp_us, values, 3);
236}
237
238/*****************************************************************************************************/
239
240static void HIDAPI_DriverSteamDeck_RegisterHints(SDL_HintCallback callback, void *userdata)
241{
242 SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_STEAMDECK, callback, userdata);
243}
244
245static void HIDAPI_DriverSteamDeck_UnregisterHints(SDL_HintCallback callback, void *userdata)
246{
247 SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_STEAMDECK, callback, userdata);
248}
249
250static bool HIDAPI_DriverSteamDeck_IsEnabled(void)
251{
252 return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_STEAMDECK,
253 SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT));
254}
255
256static bool HIDAPI_DriverSteamDeck_IsSupportedDevice(
257 SDL_HIDAPI_Device *device,
258 const char *name,
259 SDL_GamepadType type,
260 Uint16 vendor_id,
261 Uint16 product_id,
262 Uint16 version,
263 int interface_number,
264 int interface_class,
265 int interface_subclass,
266 int interface_protocol)
267{
268 return SDL_IsJoystickSteamDeck(vendor_id, product_id);
269}
270
271static bool HIDAPI_DriverSteamDeck_InitDevice(SDL_HIDAPI_Device *device)
272{
273 int size;
274 Uint8 data[64];
275 SDL_DriverSteamDeck_Context *ctx;
276
277 ctx = (SDL_DriverSteamDeck_Context *)SDL_calloc(1, sizeof(*ctx));
278 if (ctx == NULL) {
279 return false;
280 }
281
282 // Always 1kHz according to USB descriptor, but actually about 4 ms.
283 ctx->update_rate_us = 4000;
284
285 device->context = ctx;
286
287 // Read a report to see if this is the correct endpoint.
288 // Mouse, Keyboard and Controller have the same VID/PID but
289 // only the controller hidraw device receives hid reports.
290 size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 16);
291 if (size == 0)
292 return false;
293
294 if (!DisableDeckLizardMode(device->dev))
295 return false;
296
297 HIDAPI_SetDeviceName(device, "Steam Deck");
298
299 return HIDAPI_JoystickConnected(device, NULL);
300}
301
302static int HIDAPI_DriverSteamDeck_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id)
303{
304 return -1;
305}
306
307static void HIDAPI_DriverSteamDeck_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index)
308{
309}
310
311static bool HIDAPI_DriverSteamDeck_UpdateDevice(SDL_HIDAPI_Device *device)
312{
313 SDL_DriverSteamDeck_Context *ctx = (SDL_DriverSteamDeck_Context *)device->context;
314 SDL_Joystick *joystick = NULL;
315 int r;
316 uint8_t data[64];
317 ValveInReport_t *pInReport = (ValveInReport_t *)data;
318
319 if (device->num_joysticks > 0) {
320 joystick = SDL_GetJoystickFromID(device->joysticks[0]);
321 if (joystick == NULL) {
322 return false;
323 }
324 } else {
325 return false;
326 }
327
328 if (ctx->watchdog_counter++ > 200) {
329 ctx->watchdog_counter = 0;
330 if (!FeedDeckLizardWatchdog(device->dev))
331 return false;
332 }
333
334 SDL_memset(data, 0, sizeof(data));
335
336 do {
337 r = SDL_hid_read(device->dev, data, sizeof(data));
338
339 if (r < 0) {
340 // Failed to read from controller
341 HIDAPI_JoystickDisconnected(device, device->joysticks[0]);
342 return false;
343 } else if (r == 64 &&
344 pInReport->header.unReportVersion == k_ValveInReportMsgVersion &&
345 pInReport->header.ucType == ID_CONTROLLER_DECK_STATE &&
346 pInReport->header.ucLength == 64) {
347 HIDAPI_DriverSteamDeck_HandleState(device, joystick, pInReport);
348 }
349 } while (r > 0);
350
351 return true;
352}
353
354static bool HIDAPI_DriverSteamDeck_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
355{
356 SDL_DriverSteamDeck_Context *ctx = (SDL_DriverSteamDeck_Context *)device->context;
357 float update_rate_in_hz = 1.0f / (float)(ctx->update_rate_us) * 1.0e6f;
358
359 SDL_AssertJoysticksLocked();
360
361 // Initialize the joystick capabilities
362 joystick->nbuttons = SDL_GAMEPAD_NUM_STEAM_DECK_BUTTONS;
363 joystick->naxes = SDL_GAMEPAD_AXIS_COUNT;
364 joystick->nhats = 1;
365
366 SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, update_rate_in_hz);
367 SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, update_rate_in_hz);
368
369 return true;
370}
371
372static bool HIDAPI_DriverSteamDeck_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
373{
374 int rc;
375 Uint8 buffer[HID_FEATURE_REPORT_BYTES + 1] = { 0 };
376 FeatureReportMsg *msg = (FeatureReportMsg *)(buffer + 1);
377
378 msg->header.type = ID_TRIGGER_RUMBLE_CMD;
379 msg->payload.simpleRumble.unRumbleType = 0;
380 msg->payload.simpleRumble.unIntensity = HAPTIC_INTENSITY_SYSTEM;
381 msg->payload.simpleRumble.unLeftMotorSpeed = low_frequency_rumble;
382 msg->payload.simpleRumble.unRightMotorSpeed = high_frequency_rumble;
383 msg->payload.simpleRumble.nLeftGain = 2;
384 msg->payload.simpleRumble.nRightGain = 0;
385
386 rc = SDL_hid_send_feature_report(device->dev, buffer, sizeof(buffer));
387 if (rc != sizeof(buffer))
388 return false;
389 return true;
390}
391
392static bool HIDAPI_DriverSteamDeck_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
393{
394 return SDL_Unsupported();
395}
396
397static Uint32 HIDAPI_DriverSteamDeck_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
398{
399 return SDL_JOYSTICK_CAP_RUMBLE;
400}
401
402static bool HIDAPI_DriverSteamDeck_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
403{
404 return SDL_Unsupported();
405}
406
407static bool HIDAPI_DriverSteamDeck_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size)
408{
409 return SDL_Unsupported();
410}
411
412static bool HIDAPI_DriverSteamDeck_SetSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled)
413{
414 // On steam deck, sensors are enabled by default. Nothing to do here.
415 return true;
416}
417
418static void HIDAPI_DriverSteamDeck_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
419{
420 // Lizard mode id automatically re-enabled by watchdog. Nothing to do here.
421}
422
423static void HIDAPI_DriverSteamDeck_FreeDevice(SDL_HIDAPI_Device *device)
424{
425}
426
427SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSteamDeck = {
428 SDL_HINT_JOYSTICK_HIDAPI_STEAMDECK,
429 true,
430 HIDAPI_DriverSteamDeck_RegisterHints,
431 HIDAPI_DriverSteamDeck_UnregisterHints,
432 HIDAPI_DriverSteamDeck_IsEnabled,
433 HIDAPI_DriverSteamDeck_IsSupportedDevice,
434 HIDAPI_DriverSteamDeck_InitDevice,
435 HIDAPI_DriverSteamDeck_GetDevicePlayerIndex,
436 HIDAPI_DriverSteamDeck_SetDevicePlayerIndex,
437 HIDAPI_DriverSteamDeck_UpdateDevice,
438 HIDAPI_DriverSteamDeck_OpenJoystick,
439 HIDAPI_DriverSteamDeck_RumbleJoystick,
440 HIDAPI_DriverSteamDeck_RumbleJoystickTriggers,
441 HIDAPI_DriverSteamDeck_GetJoystickCapabilities,
442 HIDAPI_DriverSteamDeck_SetJoystickLED,
443 HIDAPI_DriverSteamDeck_SendJoystickEffect,
444 HIDAPI_DriverSteamDeck_SetSensorsEnabled,
445 HIDAPI_DriverSteamDeck_CloseJoystick,
446 HIDAPI_DriverSteamDeck_FreeDevice,
447};
448
449#endif // SDL_JOYSTICK_HIDAPI_STEAMDECK
450
451#endif // SDL_JOYSTICK_HIDAPI
452